Quality control
Data import
Load raw data. The main table contains already normalized
quantification of all sgRNAs, fold change, multiple hypothesis corrected
p-values, and fitness scores on the guide RNA and gene level. Contrary
to the processing of our
first CRISPRi library V1, much of the functionality from the
notebook was transferred into a new NExtflow CRISPRi
library pipeline available on github.
load("../data/input/result.Rdata")
df_main <- DESeq_result_table
rm(DESeq_result_table)
Data annotation
Different annotation columns are added to the main data frame,
including a short sgRNA identifier (excluding the position on the gene),
an sgRNA index (1 to 5), and genome annotation from Uniprot. The Uniprot
data is dynamically downloaded for every update of this pipeline using
their very simple API
(read_tsv("https://www.uniprot.org/uniprot/?query=taxonomy:1111708&format=tab")).
The full list of columns that can be queried is available here.
Pathway annotation from KEGG is later in the pipeline added using the
KEGGREST package.
df_main <- df_main %>%
group_by(sgRNA_target) %>%
mutate(sgRNA_type = if_else(grepl("^nc_", sgRNA), "ncRNA", "gene")) %>%
ungroup %>%
# map trivial names to LocusTags using a manually curated list
left_join(
read_tsv("../data/input/mapping_trivial_names.tsv", col_types = cols()),
by = c("sgRNA_target" = "gene")) %>%
# split condition into separate cols
separate(condition, into = c("carbon", "light", "treatment_1", "treatment_2"),
sep = ", ", remove = FALSE, fill = "right") %>%
unite("treatment", treatment_1, treatment_2, sep = ", ", na.rm = TRUE)
Overview about the different conditions.
df_cultivation_summary <- df_main %>% group_by(condition) %>%
summarize(
time_points = paste(unique(time), collapse = ", "),
carbon = unique(carbon),
light = unique(light),
treatment = unique(treatment),
min_fit = min(fitness),
med_fit = median(fitness),
max_fit = max(fitness))
print(df_cultivation_summary)
write_csv(df_cultivation_summary, file = "../data/output/cultivation_summary.csv")
Retrieve gene info from uniprot and merge with main data frame. We
need to make a custom function to retrieve and parse the data from
uniprot, because of a bug in the security level on Ubuntu 20.04. The
fallback option is to load a local copy of uniprot annotation for this
organism.
uniprot_url <- paste0(
"https://rest.uniprot.org/uniprotkb/stream?fields=accession",
"%2Cid%2Cprotein_name%2Cgene_names%2Corganism_name%2Clength",
"%2Cgene_oln%2Cgene_primary%2Ccc_pathway%2Cgo_id%2Ccc_subce",
"llular_location%2Ccc_interaction%2Cxref_kegg&format=tsv&query=%28prote",
"ome%3AUP000001425%29"
)
get_uniprot <- function(url) {
df_uniprot <- tryCatch({
read_tsv(uniprot_url,
col_types = cols(),
name_repair = function(x){str_replace_all(tolower(x), "[ \\.]", "_")})
},
error = function(server_error) {
message("Uniprot server not available, falling back on local Uniprot DB copy")
read_tsv("../data/input/uniprot_synechocystis.tsv", col_types = cols())
}
)
}
df_uniprot <- get_uniprot(uniprot_url) %>%
rename(locus = `kegg`, gene_name = `gene_names`,
gene_name_short = `gene_names_(primary)`, protein = `protein_names`,
uniprot_id = `entry`
) %>%
mutate(locus = str_remove_all(locus, "syn:|;$")) %>%
separate_rows(locus, gene_name_short, sep = ";") %>%
filter(!is.na(locus), !duplicated(locus))
df_main <- left_join(
df_main, df_uniprot %>% select(-`gene_names_(ordered_locus)`,
-`pathway`, -`gene_ontology_ids`, -`interacts_with`),
by = "locus")
Number of sgRNAs
Each gene is represented by up to five sgRNAs. We can test if all or
only some of the 5 sgRNAs are “behaving” in the same way in the same
conditions, more mathematically speaking we can estimate the correlation
of every sgRNA with another. First let’s summarize how many genes have
5, 4, 3 sgRNAs and so on associated with them.
# N unique sgRNAs in dataset
paste0("Number of unique sgRNAs: ", unique(df_main$sgRNA) %>% length)
[1] "Number of unique sgRNAs: 21470"
# N genes with 1,2,3,4 or 5 sgRNAs
plot_sgRNAs_per_gene <- df_main %>%
group_by(sgRNA_type, sgRNA_target) %>%
summarize(n_sgRNAs = length(unique(sgRNA_position)), .groups = "drop_last") %>%
count(n_sgRNAs) %>% filter(n_sgRNAs <= 5) %>%
ggplot(aes(x = factor(n_sgRNAs, 5:1), y = n, label = n)) +
geom_col(show.legend = FALSE) +
geom_text(size = 3, nudge_y = 200, color = grey(0.5)) +
facet_grid(~ sgRNA_type) +
labs(x = "sgRNAs / target", y = "targets") +
coord_cartesian(ylim = c(-50, 3500)) +
custom_theme()
print(plot_sgRNAs_per_gene)

Fitness distribution
of all conditions
Before biological analysis continues, we need to check if fitness
(and log2 FC from which it is calculated) is equally distributed. For
example, strictly essential genes like ribosomal genes should show the
same degreee of depletion over time, regardless of condition.
We can compare fitness over all conditions using a scatter plot
matrix. We can see that some conditions are very similar to each other,
for example the conditions treated with glucose (LC, LL +g,
LC, LL, +D, +G, HC, LL +g). Others are more
dissimilar to the rest, for example LC, IL and
LC, LL, +FL. They are more alike each other, although
LC, LL, +FL should be more comparable to
LC, LL, hinting at experimental bias. In this case both of
these conditions (and LC, LL, +G) were pre-cultivated in
low light instead of high light, as opposed to the rest of the
samples.
df_main %>% filter(time == 0, sgRNA_index == 1) %>%
select(locus, condition, fitness) %>%
filter(!is.na(locus)) %>%
pivot_wider(names_from = condition, values_from = fitness) %>%
select(-locus) %>%
custom_splom(pch = 19, cex = 0.3, col = grey(0.4, 0.4), pscales = 0)

Another way to look at the result of the normalization is to compare
the global distribution of log2 FC values, as a density plot.
library(ggridges)
df_main %>% filter(time == 10) %>%
select(sgRNA, condition, log2FoldChange) %>%
distinct %>%
ggplot(aes(x = log2FoldChange, y = condition, group = condition)) +
geom_density_ridges(fill = "#00AFBB99", col = grey(0.4)) +
lims(x = c(-2, 1.5)) +
custom_theme()

Gene fitness
SgRNA fitness score was calculated as the AUC of log2FC read number
over generation time, normalized by generation time. Gene fitness score
was calculated as the weighted mean of individual sgRNA fitness scores.
The weights are made up of two different components,
- guide RNA correlation
- guide RNA efficiency
Guide RNA
correlation
A correlation score was calculated by computing the correlation
coefficient of all sgRNAs to each other. This score is robustly
summarized by taking the median, and rescaling it from the respective
minima and maxima [-1, 1] to [0, 1]. This score served as a weight
component for each sgRNA to calculate the (global) weighted mean of log2
FC and fitness over all sgRNAs. The score has the characteristic that it
gives a weight of 1 for an sgRNA perfectly correlated with all other
sgRNAs of the same gene, and a weight of 0 for sgRNAs perfectly
anti-correlated to the other sgRNAs.
For a matrix of \(x = 1 .. m\)
sgRNAs and \(y = 1 .. n\) observations
(measurements), the correlation \(R\)
of one sgRNA to another is calculated using Pearson’s method:
\(R_x=cor([log_2FC_{x1,y1} ...
log_2FC_{x1,yn}], [log_2FC_{x2,y1} ... log_2FC_{x2,yn}])\)
The correlation weight of one sgRNA is then calculated as median of
all \(R\) rescaled between 0 and 1.
\(w_x = \frac{1 + median(R_1, R_2, ...,
R_m)}{2}\)
The following example shows the correlation matrix for the 5
rps10 sgRNAs, and their weights. The self correlation of
each sgRNA (R = 1) is removed prior to weight determination.
cor_matrix <- df_main %>% filter(sgRNA_target == "rps10") %>% ungroup %>%
select(sgRNA_index, log2FoldChange, condition, time) %>%
pivot_wider(names_from = c("condition", "time"), values_from = log2FoldChange) %>%
arrange(sgRNA_index) %>% column_to_rownames("sgRNA_index") %>%
as.matrix %>% t %>% cor(method = "pearson")
weights <- cor_matrix %>% replace(., . == 1, NA) %>%
apply(2, function(x) median(x, na.rm = TRUE)) %>%
rescale(from = c(-1, 1), to = c(0, 1))
# plot heatmap
lattice::levelplot(cor_matrix %>% replace(., . == 1, NA),
col.regions = custom_range(20))

# print weights
weights
1 2 3 4 5
0.8490072 0.7894025 0.4345219 0.8341260 0.7745213
Guide RNA
efficiency
The correlation of each sgRNA with each other is a “global” parameter
as it is identical over all conditions. A second global parameter,
sgRNA efficiency, was obtained using a similar
approach. We expect that fitness of all sgRNAs for one gene is not
normally distributed because sgRNAs are not ideal replicate
measurements. They are biased by position effects and off-target
binding, see Wang et al.,
Nature Comms, 2018 for a very insightful and comprehensive analysis
of the number and position of sgRNAs required to estimate gene
fitness.
Here, sgRNA efficiency \(E\) was
calculated as the median absolute fitness (AUC of log2FC over time) of
an sgRNA \(x = 1 .. m\) over all
observations [conditions] \(y = 1 ..
n\).
\(E_x=median(abs(fitness_{x1, y1},
fitness_{x1, y2}, ..., fitness_{x1, yn}))\)
To normalize between all sgRNAs, \(E\) is rescaled to a range between 0 and
1.
\(E_x=\frac{E_x}{max(E_1, E_2, ...,
E_m)}\)
This is the resulting sgRNA efficiency for the example gene above,
rps10.
df_main %>% filter(sgRNA_target == "rps10") %>% ungroup %>%
select(sgRNA_index, sgRNA_efficiency) %>% distinct %>%
arrange(sgRNA_index) %>% deframe
1 2 3 4 5
1.00000000 0.15916697 0.03206224 0.19239830 0.51606896
Position bias of
sgRNA repression
Plot the weight of each sgRNA to see if there is a
dependency between correlation and sgRNA position. There is no
significant trend.
We can also quantify how many genes have strongly correlated sgRNAs
and how many have outliers. In order to do this, the median weight of
the (up to) 5 sgRNAs per gene is plotted. Generally, the median weight
ranges between 0.5 and 1.0, showing on average good correlation.
plot_sgRNA_correlation <- df_main %>%
select(sgRNA_target, sgRNA_index, sgRNA_correlation, sgRNA_type) %>%
filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
distinct %>%
# plot
ggplot(aes(x = factor(sgRNA_index), y = sgRNA_correlation)) +
geom_boxplot(outlier.shape = "") +
labs(x = "sgRNA position (relative)", y = "correlation") +
stat_summary(fun.data = function(x) c(y = median(x)+0.07,
label = round(median(x), 2)), geom = "text", size = 3) +
stat_summary(fun.data = function(x) c(y = 1.1,
label = length(x)), geom = "text", color = grey(0.5), size = 3) +
coord_cartesian(ylim = c(-0.15, 1.15)) +
custom_theme()
plot_sgRNA_correlation_hist <- df_main %>%
select(sgRNA_target, sgRNA_index, sgRNA_correlation, sgRNA_type) %>%
filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
distinct %>% group_by(sgRNA_target) %>%
summarize(
median_sgRNA_correlation = median(sgRNA_correlation),
min_sgRNA_correlation = min(sgRNA_correlation)
) %>%
# plot
ggplot(aes(x = median_sgRNA_correlation)) +
geom_histogram(bins = 40, fill = custom_colors[1], alpha = 0.7) +
custom_theme()
ggarrange(plot_sgRNA_correlation, plot_sgRNA_correlation_hist, ncol = 2)

Second, the binding position of the sgRNAs could be correlated to the
strength of repression. In other words sgRNAs binding closer to the
promoter could have stronger ability to repress a gene, see Figure 1 B
in Wang et
al., Nature Comms, 2018. We plot sgRNA efficiency
for genes only, because the absolute majority of those has 5 sgRNAs.
plot_sgRNA_efficiency <- df_main %>%
filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
select(sgRNA_target, sgRNA_index, sgRNA_efficiency) %>% distinct %>%
ggplot(aes(x = factor(sgRNA_index), y = sgRNA_efficiency)) +
geom_boxplot(notch = FALSE, outlier.shape = ".") +
labs(x = "sgRNA position (relative)", y = "repression efficiency") +
coord_cartesian(ylim = c(-0.15, 1.15)) +
stat_summary(fun.data = function(x) c(y = median(x)+0.07,
label = round(median(x), 2)), geom = "text", size = 3) +
stat_summary(fun.data = function(x) c(y = 1.1,
label = length(x)), geom = "text", color = grey(0.5), size = 3) +
custom_theme()
plot_sgRNA_efficiency_hist <- df_main %>%
filter(sgRNA_index <= 5, sgRNA_type == "gene") %>%
select(sgRNA_target, sgRNA_position, sgRNA_efficiency) %>% distinct %>%
group_by(sgRNA_position) %>%
summarize(sgRNA_efficiency = median(sgRNA_efficiency), n_pos = n()) %>%
filter(n_pos >= 10) %>%
ggplot(aes(x = sgRNA_position, y = sgRNA_efficiency)) +
labs(x = "sgRNA position (nt)", y = "repression efficiency") +
geom_point(col = alpha(custom_colors[5], 0.5)) +
geom_smooth() +
custom_theme()
ggarrange(plot_sgRNA_efficiency, plot_sgRNA_efficiency_hist, ncol = 2)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

plot_selected_sgRNAs <- df_main %>%
filter(
grepl("ctrl[1-5]$|rps10$", sgRNA_target),
condition %in% c("HC, HL", "HC, LL", "LC, IL", "LC, LL")) %>%
mutate(
sgRNA_index2 = as.numeric(str_extract(sgRNA_target, "[1-9]$")),
sgRNA_index = case_when(sgRNA_position == 0 ~ sgRNA_index2, TRUE ~ sgRNA_index),
sgRNA_target = str_extract(sgRNA_target, "[a-zA-Z]*")
) %>%
ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
geom_line(size = 1) + geom_point(size = 2) +
labs(x = "time [h]", y = expression("log"[2]*" FC"))+
facet_grid(sgRNA_target ~ condition) +
custom_theme(legend.position = 0) +
coord_cartesian(ylim = c(-4.5, 2.5)) +
scale_x_continuous(breaks = c(0,2,4,6,8,10)) +
scale_color_manual(values = custom_range(5))
plot_selected_sgRNAs

Export supplemental figure with all ribosomal genes
(rpsNN/rplNN).
plot_sgRNAs_ribosome <- df_main %>%
filter(str_detect(sgRNA_target, "rp[sl][0-9]*$")) %>%
filter(condition == "LC, LL") %>%
ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
geom_line(size = 1) + geom_point(size = 2) +
facet_wrap(~ sgRNA_target, ncol = 7) +
custom_theme(legend.position = "top") +
scale_color_manual(values = custom_range(5))
print(plot_sgRNAs_ribosome)

We generate a reduced table focusing on gene fitness insteas of sgRNA
fitness. The data set already contains guide RNA level p-values from
DESeq2 (pval, padj), and gene-based p-values
from from Wilcoxon Rank Sum test (p_fitness,
p_fitness_adj). Since statistical significance was tested
for many genes in parallel, the second columns of p-values represents
multiple-hypothesis corrected p-values. The Benjamini-Hochberg method
was used for this purpose. The pipeline also outputs a score that takes
both effect size and p-value into account (comb_score),
according to the publication from Wang et al., Nat
Comm, 2018. This score is simply the absolute fitness score
multiplied by the negative log10 p-value.
df_gene <- df_main %>%
select(sgRNA_target, sgRNA_type, locus,
gene_name, condition,
carbon, light, treatment, time,
wmean_log2FoldChange, sd_log2FoldChange,
wmean_fitness, sd_fitness,
p_fitness_adj, comb_score
) %>% distinct
Global distribution
of gene fitness
Global distribution of weighted mean fitness for all genes. Effect of
ncRNA repression seems to be much lower than effect of gene
repression.
df_hist_stats <- df_gene %>%
filter(time == 0) %>%
mutate(iv = cut(wmean_fitness, breaks = c(-4,-2,2,4))) %>%
group_by(condition, iv) %>%
count() %>%
mutate(percent = 100*n/length(unique(df_gene$sgRNA_target))) %>%
separate(iv, into = c("x1", "x2"), sep = ",") %>%
mutate(x1 = as.numeric(str_extract(x1, "\\-?[0-9]")) + 0.2) %>%
mutate(x2 = as.numeric(str_extract(x2, "\\-?[0-9]")) - 0.2) %>%
filter(!is.na(x1))
plot_all_fitness_hist <- df_gene %>%
filter(time == 0) %>%
ggplot(aes(x = wmean_fitness)) +
geom_vline(xintercept = c(-2, 2), col = grey(0.5), linetype = 2) +
geom_histogram(bins = 100, aes(fill = sgRNA_type)) +
geom_bracket(data = df_hist_stats,
mapping = aes(xmin = x1, xmax = x2, label = paste0(round(percent, 1), "%")),
y.position = 800, color = grey(0.5),
label.size = 3) +
labs(x = "fitness", y ="") +
coord_cartesian(xlim = c(-4, 4), ylim = c(0, 1000)) +
facet_wrap( ~ condition, ncol = 6) +
custom_theme(legend.position = "bottom", aspect = 1) +
scale_fill_manual(values = custom_colors[c(3:4)])
print(plot_all_fitness_hist)

Gene fitness vs
significance
plot_all_fitness_volc <- df_gene %>%
arrange(sgRNA_type) %>%
ggplot(aes(x = wmean_fitness, y = -log10(p_fitness_adj), col = sgRNA_type)) +
geom_point(alpha = 1, size = 0.1) +
geom_line(data = data.frame(x = c(seq(-8, -0.5, 0.1), seq(0.5, 8, 0.1)),
y = 4/c(seq(8, 0.5, -0.1), seq(0.5, 8, 0.1))),
aes(x = x, y = y, shape = NULL), lty = 2, color = grey(0.5), size = 0.6) +
coord_cartesian(xlim = c(-7, 7), ylim = c(0, 2.5)) +
custom_theme(aspect = 1, legend.position = "bottom", legend.key.size = unit(0.4, "cm")) +
facet_wrap(~ condition, ncol = 6) +
labs(x = "fitness", y = expression("-log"[10]*" p-value")) +
scale_color_manual(values = custom_colors[3:4]) +
scale_shape_manual(values=c(1, 19))
print(plot_all_fitness_volc)

Behavior of control
sgRNAs
Ten sgRNAs were included in the library that have no gene-specific
targets. The following plot shows that these negative controls do not
have an effect on strain fitness, except probably 2 sgRNAs in one
specific condition.
plot_controls_sgRNAs <- df_main %>% filter(grepl("ctrl", sgRNA_target)) %>%
ggplot(aes(x = time, y = log2FoldChange, color = sgRNA_target)) +
geom_line(size = 1) + geom_point(size = 2) + ylim(-5, 5) +
facet_wrap(~ condition, ncol = 4) +
custom_theme() +
scale_color_manual(values = custom_range(10))
print(plot_controls_sgRNAs)

Export draft Figure 1 for manuscript.
svg(filename = "../figures/figure1.svg", width = 7, height = 8.5)
ggarrange(ncol = 2, nrow = 3,
heights = c(c(0.3, 0.3, 0.4)), widths = c(0.6, 0.4),
labels = c("A", "C", "B", "D", "E"), font.label = list_fontpars,
plot_sgRNAs_per_gene + theme(plot.margin = unit(c(12,12,12,12), "points")),
plot_sgRNA_efficiency + theme(plot.margin = unit(c(26,12,12,12), "points")),
plot_selected_sgRNAs + theme(plot.margin = unit(c(12,-4,12,14), "points")),
plot_sgRNA_correlation + theme(plot.margin = unit(c(26,12,12,12), "points")),
plot_all_fitness_hist + theme(plot.margin = unit(c(-36,-190,-48, 12), "points"))
)
dev.off()
null device
1
Gene enrichment
To plot gene fitness for the enzymes of central carbon metabolism, we
need a complete list of enzymes and the genes that they are mapped to.
To list the different KEGG databases that can be
queried, use listDatabases(). Gene-pathway mappings are
obtained and merged with pathway names and gene/enzyme names.
# get mapping of pathways for each gene
df_kegg <- keggLink("pathway", "syn") %>%
enframe(name = "locus", value = "kegg_pathway_id") %>%
# get list of pathways with name/ID pairs
left_join(by = "kegg_pathway_id",
keggList("pathway", "syn") %>%
enframe(name = "kegg_pathway_id", value = "kegg_pathway")
) %>%
# get list of gene/enzyme names
left_join(by = "locus",
keggList("syn") %>%
enframe(name = "locus", value = "kegg_gene") %>%
mutate(kegg_gene_short = str_extract(kegg_gene, "^[a-zA-Z0-9]*;") %>%
str_remove(";"))
) %>%
# trim useless prefixes
mutate(
locus = str_remove(locus, "syn:"),
kegg_pathway_id = str_remove(kegg_pathway_id, "path:"),
kegg_pathway = str_remove(kegg_pathway, " - Synechocystis sp. PCC 6803")
)
head(df_kegg)
Fitness per
pathway
Sometimes even small effects in fitness can be relevant if several
genes of the same pathway (or iso-enzymes) are affected. A simple
fitness threshold will not reveal those changes. In such cases a more
nuanced approach can be taken, a gene set enrichment analysis (GSEA).
Several packages exist to test if functionally related genes are
enriched, depleted, or both at the same time / the same conditions.
Before we test for enrichment of associated pathways/GO terms, we can
have a look at the general depletion/enrichment per KEGG pathway. The
fitness distribution per pathway can be visualized using a violin- or
scatter plot.
plot_median_fitness_kegg <- df_gene %>% filter(time == 0) %>%
inner_join(df_kegg, by = "locus") %>%
group_by(kegg_pathway, condition) %>%
summarize(.groups = "drop",
fitness = median(wmean_fitness),
n_genes = n()
) %>% filter(n_genes >= 20) %>%
mutate(
kegg_pathway = str_remove_all(kegg_pathway, " [Mm]etabolism|Biosynthesis of "),
kegg_pathway = sapply(kegg_pathway, function(x) {
if (str_length(x) <= 15) tolower(x)
else paste0(tolower(substr(x, 1, 15)), "..")
}),
kegg_pathway = fct_reorder(kegg_pathway, fitness, .desc = TRUE)
) %>%
ggplot(aes(x = kegg_pathway, y = fitness)) +
geom_boxplot(outlier.shape = NULL, color = grey(0.5), fill = grey(0.9)) +
geom_point(aes(color = condition)) +
geom_hline(yintercept = 0, lty = 2, color = grey(0.5)) +
labs(x = "", y = "median fitness") +
custom_theme(legend.position = c(0.15, 0.15), legend.key.size = unit(0.1, "cm")) +
theme(axis.text.x = element_text(angle = 35, vjust = 1.05, hjust = 1)) +
scale_fill_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
scale_color_manual(values = colorRampPalette(custom_colors[1:5])(11))
print(plot_median_fitness_kegg)

svg(filename = "../figures/plot_median_fitness_kegg.svg", width = 7, height = 2.8)
ggarrange(ncol = 1, nrow = 1, labels = "F", font.label = list_fontpars,
plot_median_fitness_kegg + theme(plot.margin = unit(c(12,12,12,25), "points"))
)
dev.off()
null device
1
Gene enrichment
analysis (KEGG)
We use the functions kegga for KEGG enrichment analysis
and goana for GO term enrichment from the
limma package. Both functions test for over or
under-representation of genes associated with certain pathways or GO
terms. The functions don’t take the strength of differential fitness
into account (DF; the depletion/enrichment over time).
df_kegg_enrichment <- lapply(unique(df_gene$condition), function(cond) {
df_gene %>% filter(
sgRNA_type == "gene", time == 0,
condition == cond) %>%
# filter for differential fitness (DF) genes
filter(!between(wmean_fitness, -2.0, 2.0), !is.na(locus)) %>%
# perform KEGG enrichment
pull(locus) %>% kegga(species.KEGG = "syn") %>%
mutate(condition = cond)
}) %>% bind_rows
head(df_kegg_enrichment)
Now we visualize the pathways that are most enriched for DF genes. It
turns out that ribosomal proteins are extremely depleted and therefore
score high on the negative log10 p-value for pathway enrichment.
df_kegg_enrichment %>%
rename(kegg_pathway = Pathway) %>%
group_by(kegg_pathway) %>% filter(N >= 20) %>%
select(kegg_pathway, condition, P.DE) %>%
mutate(log10_p_value = -log10(P.DE), .keep = "unused") %>%
mutate(kegg_pathway = paste0(str_sub(kegg_pathway, 1, 25), "..")) %>%
# make correlation plot
pivot_wider(names_from = condition, values_from = log10_p_value) %>%
column_to_rownames(var = "kegg_pathway") %>% as.matrix %>%
corrplot(is.corr = FALSE, tl.col = grey(0.5), tl.cex = 0.8,
col = colorRampPalette(custom_colors[c(1,5,2)])(10), col.lim = c(0, 20))

Unsupervised clustering
of genes
Cluster genes by
similarity
We can load a generalized tidyverse friendly function to
cluster a name variable by a value, grouped by one or more grouping
variables. For example, cluster genes (name) by fitness (value) over
several conditions (groups). The output is a factor with re-ordered
levels.
if (!"Rtools" %in% rownames(installed.packages())) {
devtools::install_github("m-jahn/Rtools")
}
Heat map of fitness for all genes and all conditions.
library(Rtools)
plot_heatmap_all <- df_gene %>% filter(time == 0, !is.na(locus)) %>%
mutate(locus = fct_cluster(locus, condition, wmean_fitness)) %>%
mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
ggplot(aes(x = locus, y = condition, fill = wmean_fitness)) +
geom_tile() + custom_theme(legend.pos = "right") +
labs(x = paste0("genes (", length(unique(df_gene$locus)),")"), y = "") +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
limits = c(-4, 4))
print(plot_heatmap_all)

Now we can plot all genes, a subset with only
significant genes, and a dendrogram for clustering. The result is
hard to interpret. With some exceptions, most genes are grouped in broad
unspecific clusters that do not reveal clear relationships between
treatment variables and fitness outcome.
# prepare new df and plot heatmap
df_heatmap <- df_gene %>% filter(time == 0, !is.na(locus)) %>%
group_by(locus) %>%
filter(any(!between(wmean_fitness, -4, 4) & p_fitness_adj < 0.01)) %>% ungroup %>%
mutate(locus = fct_cluster(locus, condition, wmean_fitness)) %>%
mutate(wmean_fitness = wmean_fitness %>% replace(., . > 8, 8) %>% replace(., . < -8, -8))
plot_heatmap_sig <- df_heatmap %>%
ggplot(aes(x = locus, y = condition, fill = wmean_fitness)) +
geom_tile() + custom_theme(legend.pos = "right") +
labs(x = paste0("genes (", length(unique(df_heatmap$locus)),")"), y = "") +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
limits = c(-8, 8))
# prepare dist object for clustering and plot dend
dist_heatmap <- df_heatmap %>% select(locus, condition, wmean_fitness) %>%
pivot_wider(names_from = condition, values_from = wmean_fitness) %>%
column_to_rownames(var = "locus") %>% as.matrix %>%
dist
plot_cluster_dend <- dist_heatmap %>%
hclust(method = "ward.D2") %>% as.dendrogram %>%
set("branches_k_col", custom_colors[1:5], k = 5) %>%
set("branches_lwd", 0.5) %>%
as.ggdend %>%
ggplot(labels = FALSE)
# arrange both on same plot
ggarrange(nrow = 2, heights = c(0.5, 0.5),
plot_cluster_dend + theme(plot.margin = unit(c(0.1, 0.09, -0.15, 0.136),"npc")),
plot_heatmap_sig
)

Gene similarity by
dimensionality reduction methods
We use two different dimensionality reduction methods,
nMDS and t-SNE. We can check if these
methods reproduce the clustering for the significantly regulated genes
produced with hclust. Analysis shows that the small
clusters are more strongly separated from the rest.
# set a seed to obtain same pattern for stochastic methods
set.seed(321)
# run nMDS analysis
NMDS <- dist_heatmap %>% metaMDS
Run 0 stress 0.07875351
Run 1 stress 0.07875392
... Procrustes: rmse 0.0002544762 max resid 0.003174421
... Similar to previous best
Run 2 stress 0.07875425
... Procrustes: rmse 0.000174541 max resid 0.002128701
... Similar to previous best
Run 3 stress 0.07875402
... Procrustes: rmse 0.0002682146 max resid 0.003431943
... Similar to previous best
Run 4 stress 0.07875367
... Procrustes: rmse 0.0001044051 max resid 0.001139777
... Similar to previous best
Run 5 stress 0.07875372
... Procrustes: rmse 9.192768e-05 max resid 0.0009968913
... Similar to previous best
Run 6 stress 0.0787535
... New best solution
... Procrustes: rmse 6.47459e-05 max resid 0.00076579
... Similar to previous best
Run 7 stress 0.07875347
... New best solution
... Procrustes: rmse 0.000149932 max resid 0.001775701
... Similar to previous best
Run 8 stress 0.07875417
... Procrustes: rmse 0.0001561166 max resid 0.001996966
... Similar to previous best
Run 9 stress 0.07875407
... Procrustes: rmse 0.0002721831 max resid 0.003442208
... Similar to previous best
Run 10 stress 0.07875366
... Procrustes: rmse 0.0001259477 max resid 0.001368973
... Similar to previous best
Run 11 stress 0.07875437
... Procrustes: rmse 0.0001884915 max resid 0.00241518
... Similar to previous best
Run 12 stress 0.07875413
... Procrustes: rmse 0.0002791112 max resid 0.003533379
... Similar to previous best
Run 13 stress 0.07875415
... Procrustes: rmse 0.0002844956 max resid 0.003600152
... Similar to previous best
Run 14 stress 0.07875426
... Procrustes: rmse 0.0003004524 max resid 0.003790509
... Similar to previous best
Run 15 stress 0.07875361
... Procrustes: rmse 5.996707e-05 max resid 0.000589186
... Similar to previous best
Run 16 stress 0.07875442
... Procrustes: rmse 0.0001811358 max resid 0.002313352
... Similar to previous best
Run 17 stress 0.07875374
... Procrustes: rmse 0.0002147258 max resid 0.002686534
... Similar to previous best
Run 18 stress 0.07875374
... Procrustes: rmse 6.828352e-05 max resid 0.0008473415
... Similar to previous best
Run 19 stress 0.07875347
... Procrustes: rmse 2.309909e-05 max resid 0.0001711964
... Similar to previous best
Run 20 stress 0.07875422
... Procrustes: rmse 0.0002872007 max resid 0.003633335
... Similar to previous best
*** Best solution repeated 14 times
df_nmds <- NMDS$points %>% as_tibble(rownames = "locus") %>%
left_join(enframe(name = "locus", value = "cluster",
cutreeord(hclust(dist_heatmap, method = "ward.D2"), k = 5)))
Joining, by = "locus"
# run t-SNE analysis
SNE <- dist_heatmap %>% tsne(max_iter = 500, perplexity = 8)
sigma summary: Min. : 0.32744177191949 |1st Qu. : 0.497094375340161 |Median : 0.550205664540542 |Mean : 0.601396916256558 |3rd Qu. : 0.65740540124671 |Max. : 1.51870623960614 |
Epoch: Iteration #100 error is: 15.8703802715933
Epoch: Iteration #200 error is: 0.563785293850083
Epoch: Iteration #300 error is: 0.543427991783255
Epoch: Iteration #400 error is: 0.535275544983855
Epoch: Iteration #500 error is: 0.531090157899214
df_tsne <- SNE %>% setNames(c("x", "y")) %>% as_tibble %>%
mutate(locus = unique(df_heatmap$locus)) %>%
left_join(enframe(name = "locus", value = "cluster",
cutreeord(hclust(dist_heatmap, method = "ward.D2"), k = 5)))
Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
Using compatibility `.name_repair`.Joining, by = "locus"
plot_nmds <- df_nmds %>%
ggplot(aes(x = MDS1, y = MDS2, color = factor(cluster))) +
geom_point(size = 2) + labs(title = "nMDS") +
custom_theme(legend.position = c(0.85, 0.78)) +
scale_color_manual(values = custom_colors)
plot_tsne <- df_tsne %>%
ggplot(aes(x = V1, y = V2, color = factor(cluster))) +
geom_point(size = 2) + labs(title = "t-SNE") +
custom_theme(legend.position = c(0.85, 0.78)) +
scale_color_manual(values = custom_colors)
ggarrange(ncol = 2, plot_nmds, plot_tsne)

Fit multiple linear
regression models
We can find clusters of genes with similar fitness, but it is also
important to identify why they cluster together. In order to
find out which variables determine the fitness outcome of a
gene, we can perform multiple linear regression. Each
gene needs to have fitness outcomes annotated with the different (mixed)
variables carbon, light,
treatment. The latter can be subdivided in individual
treatment columns glucose, DCMU, fluctuating light, and so on. Multiple
linear regression fits a linear model of the following form to the
data:
response ~ intercept + predictor A x slope A + predictor B x slope B x ...
Here, fitness is the response variable, the different
conditions are the predictors. It is important to convert the
categorical predictors into (numerical) dummy variables. Then for each
individual gene, multiple linear models are fitted and the power of each
predictor variable to predict the response is extracted.
# fixed model with 6 predictor variables -- dynamic layout would
# be better in future
fit_linreg <- function(y, x1, x2, x3, x4, x5, x6){
fit <- lm(y ~ x1 + x2 + x3 + x4 + x5 + x6)
c(coefficients(fit), summary(fit)$coefficients[, 4],
summary(fit)$r.squared)
}
# recode categorical to numerical (dummy) variables
df_linreg <- df_main %>%
filter(!is.na(locus)) %>%
select(locus, carbon, light, treatment, fitness) %>% distinct %>%
mutate(
carbon = recode(carbon, `HC` = 1, `LC` = 0),
light = recode(light, `LL` = 0, `IL` = 0.5, `HL` = 1)) %>%
mutate(dummy = 1, treatment = replace(treatment, treatment == "", "-")) %>%
pivot_wider(names_from = treatment, values_from = dummy, values_fill = 0) %>%
mutate(`+G` = `+G` + `+D, +G`) %>% rename(`+D` = `+D, +G`) %>% select(-`-`) %>%
# fit model
group_by(locus) %>%
summarize(coefficient = fit_linreg(fitness, carbon, light, `-N`, `+FL`, `+G`, `+D`),
.groups = "keep") %>%
mutate(treatment = c(rep(c("intercept", "carbon", "light", "-N", "+FL", "+G", "+D"), 2) %>%
paste0(rep(c("", "pval_"), each = 7), .), "r_squared"))
Reshape the long data frame into a wide format, with slope, intercept
and r-squared in separate columns. Add gene names and tSNE
coordinates.
df_linreg <- df_linreg %>%
left_join(select(df_gene, locus, sgRNA_target) %>% distinct, by = "locus") %>%
mutate(coeff_type = case_when(
treatment == "r_squared" ~ "r_squared",
str_detect(treatment, "pval") ~ "p_value",
TRUE ~ "slope"
)) %>%
mutate(treatment = str_remove(treatment, "pval_") %>% str_replace("r_squared", "intercept")) %>%
pivot_wider(names_from = "coeff_type", values_from = "coefficient") %>%
group_by(locus) %>% fill(r_squared)
Now we can overlay the information of the best predictor variable on
the cluster map produced by tSNE, for example, and this way identify
groups of genes regulated in a similar degree, by similar variables.
plot_tsne_linreg <- df_linreg %>%
inner_join(df_tsne, by = "locus") %>%
filter(treatment != "intercept") %>%
arrange(treatment, abs(slope)) %>%
mutate(sgRNA_target = if_else(p_value <= 0.01, sgRNA_target, "")) %>%
ggplot(aes(x = V1, y = V2, size = abs(slope),
color = cut(p_value, breaks = c(0, 0.001, 0.01, 0.05, 1)), label = sgRNA_target)) +
geom_point(alpha = 0.7) +
geom_text_repel(size = 3, max.overlaps = 50, show.legend = FALSE) +
labs(title = paste0("t-SNE clustering of ", nrow(df_tsne), " genes with absolute fitness score > 4"),
subtitle = paste0("dot size encodes effect of variable from multiple linear regresson, ",
"dot color encodes p-value")) +
custom_theme(aspect = 1) +
scale_color_manual(values = c(colorRampPalette(c("#E7298A", "#E6AB02"))(3), "#B3B3B3")) +
#scale_color_gradientn(limits = c(-5, 5), colours = custom_colors[1:2]) +
scale_size_continuous(range = c(1, 6)) +
facet_wrap( ~ treatment, ncol = 2)
print(plot_tsne_linreg)

This strategy reveals a list of interesting condition-specific
genes:
- Fluctuating light:
sll0217 - Putative diflavin flavoprotein A2 (dfa2 /
Flv4)
sll0218 - Putative diflavin flavoprotein associated
protein
- Putative diflavin flavoproteins
sll1521 (Flv1) and
sll0550 (Flv3) show inverse but FL+ specific fitness
pattern but were not included in figure because of lower
significance/FC
- Mixotrophy:
sll0593 - glk, glucokinase, catalyzes P-ylation of Glc
to G6P
ssl3364 - CP12 small protein, strongly interacts with
RbcX, RbcR, Prk. Novel C-metabolism regulator
- Light:
ssr2142 ycf19, short unknown protein, interacts with
psbO and Tat membrane protein insertion system,
slr0963 sir, sulfite reductase, ferredoxin H2O + HS +
ferredoxin <-> H+ + reduced ferredoxin + sulfite, strongly
interacts with other proteins in sulfur metabolism, specifically related
to cofactor biosynthesis, cobalamin (vitamin B12) and siroheme
- Light, mixotrophy, heterotrophy: cluster of photosynthesis related
genes increase fitness when KOed: apcA,D,E, psbB,C,D
- Carbon:
sll0217 Putative diflavin flavoprotein A2 (dfa2), KO
negatively correlated with fitness with C, positive with +FL
sll0218 same behavior as dfa2, interacts with dfa2,4,
contributes to PSII stabilization, Bersanini et al.,
2017. Negative fitness score probably side effect of downstream KD
of sll0219 (Flv2). That shows essentially same pattern as
sll0217 and sll0218 but weaker effect on
fitness.
List of genes with
strong fitness correlation
The table with linear regression coefficients and p-values is
reshaped to wide format for better readability. The
kableExtra package is used to color cells for easier
recognition. Then we subset the table for each treatment in order to
spot the most interesting genes.
df_linreg_filtered <- df_linreg %>%
filter(treatment != "intercept") %>%
group_by(locus) %>%
filter(any(abs(slope) >= 2 & p_value <= 0.01)) %>%
mutate(across(where(is.numeric), ~ round(., 3))) %>%
pivot_wider(
names_from = "treatment",
values_from = c("slope", "p_value"),
names_repair = function(x) {str_remove(x, "slope_")}
)
color_table <- function(df, variable) {
filter(df, abs(.data[[variable]]) > 2) %>%
filter(if_any(paste0("p_value_", variable), ~ . <= 0.01)) %>%
select(matches("^(sg|loc|r_s|carb|light|\\-|\\+)") |
all_of(paste0("p_value_", variable))) %>%
arrange(desc(.data[[variable]])) %>%
mutate(across(3:8, ~ cell_spec(., "html", color = "white",
background = spec_color(., option = "E", scale = c(-5.5, 5.5)),
bold = TRUE))) %>%
kbl(format = "html", escape = F) %>%
kable_paper("striped", full_width = F)
}
df_linreg_filtered %>% color_table("carbon")
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_carbon |
| sll1732 |
ndhF2 |
0.552 |
2.394 |
-1.215 |
-0.668 |
-1.515 |
0.909 |
1.232 |
0.000 |
| sll1734 |
sll1734 |
0.627 |
2.29 |
-1.151 |
-0.474 |
-1.488 |
0.777 |
1.108 |
0.000 |
| sll1733 |
ndhD3 |
0.593 |
2.222 |
-1.19 |
-0.492 |
-1.38 |
0.683 |
1.075 |
0.000 |
| slr1818 |
slr1818 |
0.474 |
2.114 |
-1.223 |
-1.427 |
-1.439 |
-0.173 |
1.72 |
0.000 |
| slr1315 |
slr1315 |
0.236 |
2.034 |
-0.897 |
-1.156 |
-1.225 |
-0.13 |
1.565 |
0.001 |
| ssl3437 |
rps17 |
0.389 |
-2.121 |
0.933 |
0.93 |
1.892 |
-0.083 |
-1.245 |
0.000 |
| ssl1426 |
rpl35 |
0.394 |
-2.259 |
1.612 |
1.489 |
1.174 |
-0.407 |
-1.582 |
0.000 |
| sll0217 |
sll0217 |
0.567 |
-2.61 |
1.904 |
1.013 |
2.135 |
-0.342 |
-1.292 |
0.000 |
| sll0218 |
sll0218 |
0.554 |
-2.662 |
1.911 |
0.764 |
1.871 |
-0.321 |
-1.328 |
0.000 |
df_linreg_filtered %>% color_table("light")
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_light |
| sll1577 |
cpcB |
0.746 |
0.18 |
3.828 |
0.214 |
0.69 |
2.37 |
2.498 |
0.000 |
| slr0335 |
apcE |
0.399 |
-0.129 |
3.241 |
0.357 |
0.742 |
2.439 |
1.467 |
0.001 |
| sll1878 |
sll1878 |
0.333 |
-1.212 |
3.229 |
1.698 |
1.686 |
0.934 |
-0.263 |
0.000 |
| sll1578 |
cpcA |
0.691 |
0.114 |
3.167 |
0.244 |
0.488 |
1.941 |
2.085 |
0.000 |
| slr2067 |
apcA |
0.433 |
0.545 |
2.98 |
0.092 |
0.332 |
2.169 |
2.746 |
0.004 |
| slr0963 |
sir |
0.320 |
-1.287 |
2.898 |
0.708 |
0.921 |
-0.22 |
-0.081 |
0.001 |
| slr0947 |
slr0947 |
0.265 |
0.055 |
2.775 |
0.207 |
0.37 |
1.392 |
0.656 |
0.002 |
| slr0898 |
nirA |
0.541 |
-1.059 |
2.745 |
-0.08 |
0.55 |
-0.383 |
0.096 |
0.000 |
| sll0689 |
sll0689 |
0.310 |
-0.13 |
2.733 |
0.128 |
0.211 |
0.094 |
0.268 |
0.000 |
| slr1986 |
apcB |
0.660 |
0.736 |
2.477 |
0.255 |
0.322 |
1.868 |
2.306 |
0.000 |
| slr1302 |
slr1302 |
0.762 |
-1.071 |
2.453 |
0.087 |
0.443 |
1.215 |
-0.26 |
0.000 |
| slr0483 |
slr0483 |
0.338 |
0.169 |
2.317 |
0.483 |
0.58 |
0.771 |
0.337 |
0.000 |
| slr0447 |
amiC |
0.556 |
-1.169 |
2.302 |
0.337 |
-0.452 |
0.784 |
-0.047 |
0.000 |
| smr0008 |
psbJ |
0.739 |
0.334 |
2.282 |
0.084 |
0.33 |
2.575 |
3.097 |
0.001 |
| slr2051 |
cpcG |
0.724 |
-0.171 |
2.263 |
0.184 |
0.421 |
1.989 |
1.369 |
0.000 |
| slr1791 |
cysH |
0.553 |
-1.855 |
2.262 |
0.675 |
1.077 |
0.067 |
-1.016 |
0.000 |
| sll0148 |
sll0148 |
0.483 |
0.315 |
2.191 |
-0.119 |
-0.128 |
1.598 |
0.155 |
0.000 |
| sll0427 |
psbO |
0.668 |
0.58 |
2.18 |
0.208 |
0.446 |
2.734 |
1.412 |
0.000 |
| sll1454 |
narB |
0.234 |
-0.035 |
2.133 |
0.006 |
-0.005 |
-0.051 |
0.667 |
0.005 |
| sll1525 |
prk |
0.287 |
0.396 |
2.064 |
0.678 |
0.72 |
0.522 |
-1.468 |
0.009 |
| sll1451 |
nrtB |
0.395 |
-0.052 |
2.026 |
0.176 |
0.236 |
-0.277 |
1.127 |
0.000 |
| slr0484 |
slr0484 |
0.479 |
0.169 |
-2.003 |
-0.44 |
-0.07 |
-0.325 |
-0.033 |
0.000 |
| sll0227 |
ppiB |
0.466 |
0.535 |
-2.087 |
-0.896 |
-0.041 |
-0.523 |
0.133 |
0.000 |
| slr0293 |
gcvP |
0.359 |
1.481 |
-2.325 |
-1.469 |
-0.509 |
-0.639 |
1.519 |
0.001 |
| sll0897 |
dnaJ4 |
0.761 |
0.877 |
-2.338 |
-0.105 |
0.121 |
-0.384 |
1.389 |
0.000 |
| sll0535 |
clpX |
0.407 |
0.613 |
-2.339 |
-0.927 |
-0.923 |
-1.332 |
0.1 |
0.000 |
| sll1915 |
sll1915 |
0.256 |
0.421 |
-2.445 |
-0.174 |
-0.453 |
-1.412 |
-0.167 |
0.003 |
| slr1545 |
rpoE |
0.324 |
0.741 |
-2.493 |
-0.372 |
0.511 |
-1.025 |
0.933 |
0.001 |
| slr0643 |
slr0643 |
0.393 |
0.12 |
-2.725 |
-0.457 |
0.107 |
-0.783 |
0.22 |
0.000 |
| ssr2142 |
ycf19 |
0.445 |
0.726 |
-3.15 |
-0.156 |
0.288 |
-2.08 |
1.127 |
0.000 |
df_linreg_filtered %>% color_table("+FL")
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_+FL |
| sll0217 |
sll0217 |
0.567 |
-2.61 |
1.904 |
1.013 |
2.135 |
-0.342 |
-1.292 |
0.001 |
| sll1894 |
ribA |
0.302 |
-0.887 |
-0.31 |
-0.055 |
-2.019 |
-0.659 |
-0.706 |
0.001 |
| sll1521 |
sll1521 |
0.756 |
-0.047 |
-0.588 |
-0.318 |
-2.995 |
-0.441 |
-0.09 |
0.000 |
df_linreg_filtered %>% color_table("+G")
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_+G |
| sll1496 |
sll1496 |
0.845 |
1.316 |
-1.135 |
-0.356 |
-0.637 |
3.383 |
-2.45 |
0.000 |
| slr2070 |
slr2070 |
0.375 |
0.841 |
-0.638 |
-0.452 |
-0.694 |
2.809 |
-1.869 |
0.000 |
| sll0427 |
psbO |
0.668 |
0.58 |
2.18 |
0.208 |
0.446 |
2.734 |
1.412 |
0.000 |
| slr0758 |
slr0758 |
0.803 |
-0.651 |
1.783 |
0.275 |
-0.169 |
2.654 |
-1.546 |
0.000 |
| smr0008 |
psbJ |
0.739 |
0.334 |
2.282 |
0.084 |
0.33 |
2.575 |
3.097 |
0.000 |
| slr0335 |
apcE |
0.399 |
-0.129 |
3.241 |
0.357 |
0.742 |
2.439 |
1.467 |
0.001 |
| sll1577 |
cpcB |
0.746 |
0.18 |
3.828 |
0.214 |
0.69 |
2.37 |
2.498 |
0.000 |
| sll0851 |
psbC |
0.515 |
0.117 |
1.223 |
0.183 |
0.203 |
2.364 |
3.267 |
0.002 |
| sll1579 |
cpcC2 |
0.870 |
-0.03 |
1.024 |
0.099 |
0.175 |
2.263 |
0.588 |
0.000 |
| slr0756 |
slr0756 |
0.474 |
0.12 |
0.79 |
0.388 |
-0.203 |
2.176 |
-1.756 |
0.000 |
| slr2067 |
apcA |
0.433 |
0.545 |
2.98 |
0.092 |
0.332 |
2.169 |
2.746 |
0.007 |
| ssl2598 |
psbH |
0.628 |
0.271 |
1.556 |
0.178 |
-0.103 |
2.165 |
1.207 |
0.000 |
| sll0018 |
cbbA |
0.362 |
-0.468 |
0.938 |
0.735 |
0.536 |
2.163 |
1.558 |
0.004 |
| ssr2062 |
ssr2062 |
0.376 |
0.605 |
-0.192 |
0.181 |
-0.178 |
2.134 |
-1.875 |
0.000 |
| sll1057 |
trx |
0.789 |
-0.378 |
0.996 |
0.061 |
0.136 |
2.087 |
0.928 |
0.000 |
| sll0062 |
sll0062 |
0.683 |
0.525 |
1.026 |
0.194 |
0.564 |
2.084 |
0.908 |
0.000 |
| sll0849 |
psbD |
0.526 |
-0.32 |
1.797 |
0.57 |
0.056 |
2.034 |
2.613 |
0.002 |
| ssr2142 |
ycf19 |
0.445 |
0.726 |
-3.15 |
-0.156 |
0.288 |
-2.08 |
1.127 |
0.001 |
| sll1534 |
sll1534 |
0.412 |
-0.179 |
-1.573 |
-0.125 |
0.191 |
-2.509 |
1.019 |
0.000 |
| ssl3364 |
ssl3364 |
0.744 |
0.091 |
-1.154 |
-0.544 |
-0.17 |
-2.834 |
-0.781 |
0.000 |
| sll0593 |
glk |
0.809 |
-0.067 |
-0.441 |
-0.326 |
-0.223 |
-3.533 |
-2.005 |
0.000 |
df_linreg_filtered %>% color_table("+D")
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_+D |
| slr0653 |
rpoDI |
0.379 |
1.923 |
-0.799 |
-1.247 |
-1.386 |
-1.205 |
3.848 |
0.000 |
| slr0906 |
psbB |
0.573 |
0.125 |
1.184 |
0.272 |
-0.287 |
0.942 |
3.372 |
0.000 |
| smr0006 |
psbF |
0.700 |
0.215 |
0.922 |
0.02 |
0.002 |
0.783 |
3.285 |
0.000 |
| sll0851 |
psbC |
0.515 |
0.117 |
1.223 |
0.183 |
0.203 |
2.364 |
3.267 |
0.001 |
| ssr3451 |
psbE |
0.643 |
0.248 |
1.057 |
-0.097 |
-0.036 |
1.032 |
3.111 |
0.000 |
| smr0008 |
psbJ |
0.739 |
0.334 |
2.282 |
0.084 |
0.33 |
2.575 |
3.097 |
0.000 |
| slr2067 |
apcA |
0.433 |
0.545 |
2.98 |
0.092 |
0.332 |
2.169 |
2.746 |
0.009 |
| sll0849 |
psbD |
0.526 |
-0.32 |
1.797 |
0.57 |
0.056 |
2.034 |
2.613 |
0.002 |
| sll1577 |
cpcB |
0.746 |
0.18 |
3.828 |
0.214 |
0.69 |
2.37 |
2.498 |
0.000 |
| slr1986 |
apcB |
0.660 |
0.736 |
2.477 |
0.255 |
0.322 |
1.868 |
2.306 |
0.000 |
| sll1578 |
cpcA |
0.691 |
0.114 |
3.167 |
0.244 |
0.488 |
1.941 |
2.085 |
0.000 |
| sll0593 |
glk |
0.809 |
-0.067 |
-0.441 |
-0.326 |
-0.223 |
-3.533 |
-2.005 |
0.001 |
| slr1279 |
ndhC |
0.690 |
0.802 |
-0.207 |
0.346 |
-0.17 |
-0.936 |
-2.093 |
0.000 |
| slr1793 |
talB |
0.572 |
0.121 |
-0.126 |
-0.614 |
0.005 |
0.172 |
-2.129 |
0.000 |
| sll0329 |
gnd |
0.448 |
-0.188 |
-0.077 |
0.105 |
0.231 |
-0.908 |
-2.248 |
0.001 |
| sll1496 |
sll1496 |
0.845 |
1.316 |
-1.135 |
-0.356 |
-0.637 |
3.383 |
-2.45 |
0.000 |
| slr1098 |
slr1098 |
0.337 |
0.855 |
-2.047 |
-0.14 |
-0.375 |
0.517 |
-2.595 |
0.003 |
| slr1643 |
petH |
0.336 |
-0.946 |
1.635 |
0.735 |
1.224 |
1.64 |
-2.768 |
0.000 |
| slr1843 |
zwf |
0.728 |
-0.079 |
0.339 |
-0.069 |
0.071 |
0.008 |
-2.838 |
0.000 |
Unknown / uncharacterized genes
Based on the multiple linear model correlations, we can try to
extract a shortlist of the most interesting hypothetical
genes. These could warrant further investigations.
df_unknown_hits <- df_linreg_filtered %>%
left_join(df_uniprot, by = "locus") %>%
# filter by name: only unknown proteins
filter(
!locus %in% c("ssl3364", "sll0218", "sll1734", "slr1302"),
is.na(gene_name_short),
is.na(pathway),
str_detect(protein, "[a-zA-Z]{3}[0-9]{4} protein|Uncharacterized")) %>%
select(locus:uniprot_id) %>%
rowwise() %>% mutate(min_pval = min(c_across(matches("p_value_")))) %>%
arrange(min_pval) %>%
ungroup %>% slice(1:10)
df_unknown_hits %>%
mutate(across(4:9, ~ cell_spec(., "html", color = "white",
background = spec_color(., option = "E", scale = c(-5.5, 5.5)),
bold = TRUE))) %>%
kbl(format = "html", escape = F) %>%
kable_paper("striped", full_width = F)
| locus |
sgRNA_target |
r_squared |
carbon |
light |
-N |
+FL |
+G |
+D |
p_value_carbon |
p_value_light |
p_value_-N |
p_value_+FL |
p_value_+G |
p_value_+D |
uniprot_id |
min_pval |
| sll0062 |
sll0062 |
0.683 |
0.525 |
1.026 |
0.194 |
0.564 |
2.084 |
0.908 |
0.014 |
0.007 |
0.608 |
0.055 |
0.000 |
0.020 |
Q55148 |
0.000 |
| sll0148 |
sll0148 |
0.483 |
0.315 |
2.191 |
-0.119 |
-0.128 |
1.598 |
0.155 |
0.296 |
0.000 |
0.826 |
0.759 |
0.000 |
0.778 |
P74453 |
0.000 |
| sll1534 |
sll1534 |
0.412 |
-0.179 |
-1.573 |
-0.125 |
0.191 |
-2.509 |
1.019 |
0.648 |
0.027 |
0.860 |
0.726 |
0.000 |
0.160 |
P74348 |
0.000 |
| slr0483 |
slr0483 |
0.338 |
0.169 |
2.317 |
0.483 |
0.58 |
0.771 |
0.337 |
0.567 |
0.000 |
0.368 |
0.161 |
0.064 |
0.533 |
Q55176 |
0.000 |
| slr0643 |
slr0643 |
0.393 |
0.12 |
-2.725 |
-0.457 |
0.107 |
-0.783 |
0.22 |
0.719 |
0.000 |
0.454 |
0.819 |
0.097 |
0.720 |
Q55722 |
0.000 |
| slr1818 |
slr1818 |
0.474 |
2.114 |
-1.223 |
-1.427 |
-1.439 |
-0.173 |
1.72 |
0.000 |
0.071 |
0.040 |
0.008 |
0.739 |
0.015 |
P73706 |
0.000 |
| slr2070 |
slr2070 |
0.375 |
0.841 |
-0.638 |
-0.452 |
-0.694 |
2.809 |
-1.869 |
0.125 |
0.507 |
0.646 |
0.359 |
0.000 |
0.065 |
P73370 |
0.000 |
| ssr2062 |
ssr2062 |
0.376 |
0.605 |
-0.192 |
0.181 |
-0.178 |
2.134 |
-1.875 |
0.115 |
0.775 |
0.792 |
0.736 |
0.000 |
0.009 |
P73494 |
0.000 |
| slr1315 |
slr1315 |
0.236 |
2.034 |
-0.897 |
-1.156 |
-1.225 |
-0.13 |
1.565 |
0.001 |
0.402 |
0.291 |
0.147 |
0.876 |
0.160 |
P72589 |
0.001 |
| sll1915 |
sll1915 |
0.256 |
0.421 |
-2.445 |
-0.174 |
-0.453 |
-1.412 |
-0.167 |
0.340 |
0.003 |
0.827 |
0.460 |
0.024 |
0.836 |
P73247 |
0.003 |
The tables above show genes whose fitness is most significantly
correlated with one of the treatments. The table with unknown genes is
further used to plot fitness per condition as a line plot, in order to
inspect the trends from fitting the multiple liner regression models.
Note that this list of genes was derived from multiple linear
regression. Note that the obtained p-value used for filtering is
different from the fitness p-value for the experimental conditions.
plot_unknown_genes <- df_main %>%
filter(locus %in% unique(df_unknown_hits$locus)) %>%
mutate(locus = factor(locus, unique(df_unknown_hits$locus))) %>%
ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
geom_line(size = 1) +
custom_theme(legend.position = "top") +
labs(
title = "sgRNA profile for top 10 unknown genes according to MLR",
subtitle = "MLR filter: slope >= 2, p-value <= 0.01. Color encodes sgRNA position") +
scale_color_manual(values = custom_range(5)) +
facet_grid(locus ~ condition)
print(plot_unknown_genes)

Summary
sll0062 - small 149 AA membrane protein, phenotype:
fitness mainly decreased in LC-LL conditioms, but not
mixo/photoherotrophy
sll0148 - large 735 AA membrane protein, phenotype:
fitness mainly decreased in LL conditions, regardless of C, but not
mixo/photoherotrophy
sll1534 - medium 378 AA putative glycosyltransferase,
phenotype: fitness decreased in all conditions, but predominantly
mixo/photoherotrophy
slr0483 - small 149 AA membrane protein, potential
thylakoid protein, implicated in scaffolding for other thyl. proteins
(1,
2).
Phenotype: fitness decreased in all L conditions, but not HC+HL and
mixo/photoherotrophy (supports involvement in photosynthesis)
slr0643 - medium 493 AA transmembrane protein (9 TM
helices), phenotype: strong decrease in HC+HL only. Putative
metalloprotease, defect in acid acclimation, KO overexpresses NDH and Ci
transporters (1)
slr1818 - medium 201 AA protein, phenotype: fitness
decreased specifically in LC+IL and LC+LL+F, so only under light stress.
Phenotype similar to the flavodiiron proteins Flv1 and 3
slr2070 - medium 284 AA protein, unknown localization,
phenotype: strongly increased fitness in +G conditions only; slightly
decreased fitness in LC+IL and LC+LL+F. Suggests that protein is a
burden in non-photosynthetic conditions, but required at high L to C
ratio. Implicated as thylakoid protein (1)
ssr2062 - small 88 AA protein, localization unknown,
phenotype: strongly increased fitness in +G conditions only, as above.
Implicated in regulation of light dark adaptation by transcriptome
profiling (1,
2)
slr1315 - medium 202 AA protein, localization unknown,
Uma2 restriction endonuclease domain. Phenotype: fitness decreased
specifically in LC+IL and LC+LL+F, so predominantly under light
stress
sll1915 - small 183 AA membrane protein, phenotype:
decreased fitness predominantly in LL or HC conditions (HC,HL and
+G)
Differential fitness of
selected gene sets
Central carbon
metabolism
To plot gene fitness for the enzymes of central carbon metabolism, we
use the complete list of enzymes and the genes that they are mapped to
(obtained from KEGG). We can extract gene sets for specific pathways and
plot fitness. We start with glycolysis and Calvin cycle enzymes.
list_central_met_pathways <- c(
"Glycolysis / Gluconeogenesis",
"Pentose phosphate pathway",
"Carbon fixation in photosynthetic organisms",
"Photosynthesis",
"Citrate cycle (TCA cycle)",
"Pyruvate metabolism",
"Glyoxylate and dicarboxylate metabolism"
)
plot_gene_fitness <- function(df, pw = NULL, gene = NULL,
title = NULL, ncol = 8, legend.position = "bottom") {
df <- df %>% filter(time == 0)
if (!is.null(pw)) {
df <- df %>% inner_join(df_kegg %>% filter(kegg_pathway == pw) %>% select(locus),
by = "locus")
title <- pw
} else if (!is.null(gene)) {
df <- df %>% filter(locus %in% gene)
}
ggplot(df, aes(x = condition, y = wmean_fitness,
ymin = wmean_fitness-sd_fitness,
ymax = wmean_fitness+sd_fitness,
fill = condition,
color = condition,
label = if_else(p_fitness_adj <= 0.01, "*", ""))) +
geom_col(position = "dodge", width = 0.6) +
geom_errorbar(position = "dodge", width = 0.6, size = 1) +
geom_text(size = 5, aes(y = mapply(FUN = function(x, y) {
if (x < 0) x - y - 2
else x + y + 0.3
}, wmean_fitness, sd_fitness))) +
custom_theme(aspect.ratio = 1,
legend.position = legend.position, legend.key.size = unit(0.4, "cm")) +
labs(title = title, x = "", y = "fitness") +
theme(axis.text.x = element_blank(), axis.ticks = element_blank()) +
scale_y_continuous(breaks = seq(-10, 10, 5)) +
scale_fill_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
scale_color_manual(values = colorRampPalette(custom_colors[1:5])(11)) +
facet_wrap(~ sgRNA_target, ncol = ncol, drop = FALSE)
}
print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[1]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[2]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[3]]))

print(plot_gene_fitness(df_gene, pw = list_central_met_pathways[[5]]))

Gene fitness in
mixotrophy and heterotrophy
Using fluctuator,
we can import a custom metabolic map for Synechocystis sp. PCC
6803, and overlay published fluxes that were measured with LC-MS using
isotopically labelled carbon sources (Nakajima et al.,
2014).
Fluctuator can be installed using a function from
devtools:
if (!"fluctuator" %in% rownames(installed.packages())) {
devtools::install_github("m-jahn/fluctuator")
}
We import the metabolic flux data from the supplemental items of Nakajima et al., 2014.
library(fluctuator)
# import flux data
df_nakajima_mfa <- read.csv("../data/input/Nakajima2014_metabolic_fluxes.csv")
# generate stroke width and color
df_nakajima_mfa <- df_nakajima_mfa %>%
mutate(
stroke_width = 0.3 + (0.7*sqrt(abs(flux))),
stroke_color = abs(flux) %>% {1+(./max(.))*9} %>% round,
stroke_color_rgb = colorRampPalette(custom_colors[c(5,2,1)])(10)[stroke_color])
The next step is to overlay the fluxes. We generate two types of
maps, mixotrophy and photoheterotrophy. The stroke width and color for
all reactions is set by the flux magnitude.
for (cond in c("mixotroph", "photoheterotroph")) {
# import map
SVG_template <- read_svg("../data/input/map_central_metabolism_syn.svg")
# set stroke on SVG map
SVG_mix <- set_attributes(SVG_template,
node = filter(df_nakajima_mfa, condition == cond)$reaction,
attr = "style",
pattern = "stroke-width:[0-9]+\\.[0-9]+",
replacement = paste0("stroke-width:",
filter(df_nakajima_mfa, condition == cond)$stroke_width))
# set color
SVG_mix <- set_attributes(SVG_mix,
node = filter(df_nakajima_mfa, condition == cond)$reaction,
attr = "style",
pattern = "stroke:#b3b3b3",
replacement = paste0("stroke:",
filter(df_nakajima_mfa, condition == cond)$stroke_color_rgb))
# set arrow directionality
SVG_mix <- set_attributes(SVG_mix,
node = filter(df_nakajima_mfa, condition == cond, flux < 0)$reaction,
attr = "style",
pattern = "marker-end:url\\(#marker[0-9]*\\);",
replacement = "")
SVG_mix <- set_attributes(SVG_mix,
node = filter(df_nakajima_mfa, condition == cond, flux > 0)$reaction,
attr = "style",
pattern = "marker-start:url\\(#marker[0-9]*\\);",
replacement = "")
write_svg(SVG_mix, file = paste0("../data/output/map_", cond, "y.svg"))
}
 |
 |
Now we plot fitness of central carbon metabolism genes for two or
three selected conditions. These will be added to the metabolic map
manually. The mixotrophic conditions LC, LL, +G and
HC, LL, +G turned out to be very similar.
df_centralcarb <- tibble(
locus = c(
# EMP
"sll0593", "slr0329", "slr1349", "slr0952", "slr2094",
"sll0018", "slr0943", "slr0783", "slr0884", "sll1342",
"slr0394", "slr1945", "slr0752", "sll1275", "sll0587",
# PPP + CBB
"slr1843", "sll1479", "sll0329", "slr1793", "sll1070",
"sll0807", "slr0194", "ssl2153", "sll1525", "slr0012",
"slr0009", "ssl3364",
# Pyruvate metabolism
"sll1841", "sll1721", "slr1096", "slr1934", "sll0401",
"slr0721", "sll0920",
# TCA
"slr0665", "slr1289", "slr1096", "sll1023", "sll1557",
"slr1233", "slr0201", "sll1625", "sll0823", "slr0018",
"sll0891"),
reaction = c(
# EMP
"HEX", "HEX", "PGI", "FBP", "FBP",
"FBA", "FBA", "TPI", "GAPDH", "GAPDH",
"PGK", "PGM", "ENO", "PYK", "PYK",
# PPP + CBB
"G6PDH", "PGL", "GND", "TAL", "TKT",
"RPE", "RPI", "RPI", "PRUK", "RUBISCO",
"RUBISCO", "CP12",
# Pyruvate metabolism
"PDH", "PDH", "PDH", "PDH", "CS",
"ME", "PPC",
# TCA
"ACONT", "ICDH", "AKGDH", "SUCOAS", "SUCOAS",
"SUCD", "SUCD", "SUCD", "SUCD", "FUM",
"MDH"
))
df_centralcarb <- df_gene %>% filter(
time == 0,
condition %in% c("LC, LL", "LC, LL, +G", "LC, LL, +D, +G")) %>%
inner_join(df_centralcarb, ., by = "locus") %>%
mutate(sgRNA_target = sgRNA_target %>% fct_inorder) %>%
mutate(condition = recode(condition, `LC, LL` = "Phototrophy", `LC, LL, +G` = "Mixotrophy",
`LC, LL, +D, +G` = "Photoheterotrophy") %>% factor(., unique(.)[c(1,3,2)])) %>%
mutate(pathway = rep(c("EMP", "PPP + CBB", "Pyruvate", "TCA"), c(45,36,21,33)))
plot_centralcarb_minifig <- df_centralcarb %>%
group_by(pathway) %>% group_split %>%
lapply(function(df) {
ggplot(df, aes(x = condition, y = wmean_fitness,
ymin = wmean_fitness-sd_fitness,
ymax = wmean_fitness+sd_fitness,
fill = condition, color = condition,
label = if_else(p_fitness_adj <= 0.01, "*", ""))) +
geom_hline(yintercept = c(0, -5, -10), linetype = 3, col = grey(0.6)) +
geom_errorbar(position = "dodge", width = 0.6, size = 1, alpha = 0.7) +
geom_col(position = "dodge", width = 0.6, fill = "white", color = "white") +
geom_col(position = "dodge", width = 0.6) +
geom_text(size = 6, aes(y = mapply(FUN = function(x, y) {
if (x < 0) x - y - 3
else x + y + 0.3
}, wmean_fitness, sd_fitness))) +
custom_theme(aspect.ratio = 1, legend.position = 0) +
theme(axis.text.x = element_blank(), axis.text.y = element_blank(),
axis.ticks = element_blank(), panel.grid.major = element_blank(),
strip.text = element_text(size = 8, color = "white", margin = margin(1, 1, 1, 1, "pt")),
panel.border = element_rect(linetype = "solid", colour = grey(0.6), fill = NA, size = 1.0),
panel.background = element_rect(fill = grey(0.95), color = NA),
strip.background = element_rect(fill = grey(0.6), color = grey(0.6))
) +
labs(x = "", y = "") +
coord_cartesian(ylim = c(-11, 1)) +
scale_fill_manual(values = alpha(custom_colors[c(2,3,1)], 0.7)) +
scale_color_manual(values = alpha(custom_colors[c(2,3,1)], 0.7)) +
facet_wrap(~ sgRNA_target, ncol = 6)
})
ggarrange(nrow = 4, heights = c(0.33, 0.224, 0.224, 0.224),
labels = LETTERS[1:4], font.label = list_fontpars,
plot_centralcarb_minifig[[1]] + theme(plot.margin = unit(c(0, 0,-5,0), "points")),
plot_centralcarb_minifig[[2]] + theme(plot.margin = unit(c(0 ,0,-5,0), "points")),
plot_centralcarb_minifig[[3]] + theme(plot.margin = unit(c(0, 0,-5,0), "points")),
plot_centralcarb_minifig[[4]] + theme(plot.margin = unit(c(0, 0,-5,0), "points"))
)

Adaptation to light
and carbon excess
We will look at different types of regulatory adaptations:
- photosystem subunits, particularly PSII
apc/cpcantenna proteins (phycobilisomes),
known to be among the most expressed and regulated genes in cyanos
- flavoproteins Flv1 (
sll1521), Flv2
(sll0219), Flv3 (sll0550), Flv4
(sll0217), sll0218 (in flv2/4 operon)
- low affinity/high flux transporters Ci transporters: bicA
(
sll0834), NDH-I4 with ndhF4, D4, cupB
(sll0026, sll0027, slr1302)
- high affinity/low flux inducible Ci transporters: BCT1/cmpAB(porB)CD
(
slr0040-44), SbtA/B (slr1512,
slr1513), NDH-I3 with ndhF3, ndhD3, cupA, cupS
(sll1732-35)
- carbon transport regulatory proteins: ccmR/rbcR
(
sll1594), cmpR (sll0030), cyabrB1
(sll0359), cyabrB2 (sll0822)
plot_photosystem2 <- df_gene %>%
mutate(sgRNA_target = str_replace(sgRNA_target, "psb13", "psbW")) %>%
filter(str_detect(sgRNA_target, "psb[BCDEFHIJKLOUVXZ]$"), time == 0) %>%
plot_gene_fitness(ncol = 6, legend.position = 0)
plot_phycobilisome <- df_gene %>% filter(str_detect(gene_name, "[ac]pc[ABCDEFG]")) %>%
plot_gene_fitness(ncol = 6, legend.position = 0)
plot_flv_genes <- df_gene %>% filter(locus %in% c("sll1521", "sll0219", "sll0550", "sll0217", "sll0218")) %>%
mutate(sgRNA_target = recode(sgRNA_target, `sll1521` = "Flv1 (sll1521)", `sll0219` = "Flv2 (sll0219)",
`sll0550` = "Flv3 (sll0550)", `sll0217` = "Flv4 (sll0217)")) %>%
mutate(sgRNA_target = factor(sgRNA_target, c(unique(sgRNA_target), ""))) %>%
plot_gene_fitness(ncol = 6, legend.position = 0)
plot_carbon_uptake <- df_gene %>% filter(locus %in% c(
"sll0026", "sll0027", "slr1302", "sll1732",
"sll1733", "sll1734", "sll1735", "slr0040",
"slr0041", "slr0043", "slr0044"
)) %>%
mutate(sgRNA_target = recode(sgRNA_target,
`nrtC2` = "cmpC", `nrtD3` = "cmpD",
`sll1734` = "cupA", `slr1302` = "cupB",
`sll1735` = "cupS", `ndhF2` = "ndhF3"
)) %>%
mutate(sgRNA_target = factor(sgRNA_target, unique(sgRNA_target)[c(4,6,11,3,5,9,10,1,2,7,8)])) %>%
plot_gene_fitness(ncol = 6, legend.position = 0) +
coord_cartesian(ylim = c(-7.9, 2.4))
Figure 2 draft:
ggarrange(nrow = 4, heights = c(0.32, 0.32, 0.13, 0.23),
labels = LETTERS[1:4],
font.label = list_fontpars,
plot_photosystem2 + theme(plot.margin = unit(c(0, 12, 0, 12),"points")),
plot_phycobilisome + theme(plot.margin = unit(c(0, 12, 0, 12),"points")),
plot_flv_genes + theme(plot.margin = unit(c(4.7, 12, 4.7, 12),"points")),
plot_carbon_uptake + theme(plot.margin = unit(c(4.3, 12, 4.3, 12),"points"))
)

As a Supplementary figure to C), we can plot all other carbon
transporters and regulatory genes that showed a less remarkable
effect.
plot_carbon_uptake_2 <- df_gene %>% filter(locus %in% c(
"sll0834", "slr1512", "slr1513", "sll1594", "sll0030", "sll0359", "sll0822"
)) %>%
mutate(sgRNA_target = recode(sgRNA_target,
`sll0834` = "bicA", `slr1512` = "sbtA", `slr1513` = "sbtB",
`sll0359` = "cyabrB1", `sll0822` = "cyabrB2", `rbcR` = "ccmR"
)) %>%
mutate(sgRNA_target = factor(sgRNA_target, unique(sgRNA_target))) %>%
plot_gene_fitness(ncol = 4, legend.position = "right")
plot_carbon_uptake_2

As another Supplementary Figure, we can plot the total
protein mass of the phycobilisome determined by protein mass
spectrometry. This data was published in our study Jahn
et al., Cell Reports, 2018. The data can be downloaded directly from
the ShinyProt github page where it is included for on demand
visualization.
load(url("https://github.com/m-jahn/ShinyProt/blob/master/data/Jahn_2018_Light_and_CO2_lim.Rdata?raw=true"))
plot_protmass_phycobilisome1 <- Jahn_2018_Light_and_CO2_lim %>%
filter(str_detect(protein, "[ac]pc[ABCDEFG]"), sample != "CO2") %>%
mutate(protein = str_extract(protein, "[ac]pc[ABCDEFG][12]?")) %>%
ggplot(aes(x = factor(light), y = 100*mean_mass_fraction_norm,
fill = str_sub(protein, 1, 3),
label = if_else(str_detect(protein, "[ac]pc[C-Z][12]?"), "", protein))) +
lims(y = c(0, 22)) +
geom_col(position = "stack", width = 0.7, col = grey(1), size = 0.2) +
geom_text(size = 2.5, position = position_stack(vjust = 0.5), color = "white") +
custom_theme(legend.position = "bottom", legend.key.size = unit(0.5, "cm")) +
labs(title = "Light limitation", x = expression("µmol photons m"^-2*" s"^-1), y = "% protein mass") +
scale_fill_manual(values = c("#8eb655", "#937fb3")) +
scale_color_manual(values = c("#8eb655", "#937fb3"))
plot_protmass_phycobilisome2 <- Jahn_2018_Light_and_CO2_lim %>%
filter(str_detect(protein, "[ac]pc[ABCDEFG]"), sample == "CO2") %>%
mutate(protein = str_extract(protein, "[ac]pc[ABCDEFG][12]?")) %>%
ggplot(aes(x = factor(co2_concentration), y = 100*mean_mass_fraction_norm,
fill = str_sub(protein, 1, 3),
label = if_else(str_detect(protein, "[ac]pc[C-Z][12]?"), "", protein))) +
lims(y = c(0, 22)) +
geom_col(position = "stack", width = 0.7, col = grey(1), size = 0.2) +
geom_text(size = 2.5, position = position_stack(vjust = 0.5), color = "white") +
custom_theme(legend.position = "bottom", legend.key.size = unit(0.5, "cm")) +
labs(title = "CO2 limitation", x = "% CO2 in air", y = "% protein mass") +
scale_fill_manual(values = c("#8eb655", "#937fb3")) +
scale_color_manual(values = c("#8eb655", "#937fb3"))
ggarrange(ncol = 2, widths = c(0.5,0.5),
labels = LETTERS[1:2], font.label = list_fontpars,
plot_protmass_phycobilisome1,
plot_protmass_phycobilisome2
)

Other genes of interest that either did not show any (remarkable)
effect on fitness, or do not meet the scope of this section:
- OCP (
slr1963), pgr5 (ssr2016)
- SigB (
sll0306), SigC (sll0184), SigD
(sll2012), SigE (sll1689) (rpoD genes
1-4)
- ccmM (
sll1031), ccmK2 (sll1028), ccmK1
(sll1029), ccmN (sll1032), ccmO
(slr0436), ccmL (sll1030)
Alternative representation of photosystems gene fitness as heat
maps:
plot_sgRNAs_ps1 <- df_gene %>%
filter(str_detect(sgRNA_target, "psa[A-Z]*"), time == 0) %>%
mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
ggplot(aes(x = condition, y = fct_rev(sgRNA_target), fill = wmean_fitness)) +
geom_tile() + custom_theme() +
geom_text(size = 4, nudge_y = -0.4, color = grey(0.4),
aes(label = if_else(p_fitness_adj <= 0.01, "*", ""))) +
labs(title = "Photosystem I", x = "", y = "") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
limits = c(-4, 4))
plot_sgRNAs_ps2 <- df_gene %>%
filter(str_detect(sgRNA_target, "psb[A-Z1][23]?$"), time == 0) %>%
mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
mutate(sgRNA_target = str_replace(sgRNA_target, "psb13", "psbW")) %>%
ggplot(aes(x = condition, y = fct_rev(sgRNA_target), fill = wmean_fitness)) +
geom_tile() + custom_theme() +
geom_text(size = 4, nudge_y = -0.4, color = grey(0.4),
aes(label = if_else(p_fitness_adj <= 0.01, "*", ""))) +
labs(title = "Photosystem II", x = "", y = "") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
limits = c(-4, 4))
ggarrange(ncol = 2, plot_sgRNAs_ps1, plot_sgRNAs_ps2)

- The psbK knockdown has a strong fitness penalty in all conditions,
suggesting a universal role
- however it is not very significant in Wilcox rank sum test
- this is due to only having 3 sgRNAs, they actually all show the same
phenotype
df_main %>%
filter(sgRNA_target %in% c("psbK")) %>%
mutate(locus = factor(locus)) %>%
ggplot(aes(x = time, y = log2FoldChange, color = factor(sgRNA_index))) +
geom_line(size = 1) +
custom_theme(legend.position = "top") +
scale_color_manual(values = custom_range(5)) +
facet_wrap( ~ condition)

Genes where knock
down leads to increased fitness
list_genes_pos_fitness <- df_gene %>%
filter(time == 0, !is.na(locus), wmean_fitness > 2) %>%
pull(locus) %>% unique
plot_gene_fitness(df_gene, gene = list_genes_pos_fitness, title = "Genes with increased fitness (f > 2)")

Summary: - pmgA is once again the gene with strongest and most
widespread fitness increase, validating results from library V1 -
slr1916 same phenotype as pmgA just weaker. We also know this one from
before. Must have identical role as pmgA. - all PSII genes show
increased fitness in photoheterotrophic condition –> PS is a burden
here - sll0689, pxcA, slr1609 - all increased fitness in HC,HL, first
two are Na+/CO2 (?) trnasporters, slr1609 we know from before, annotated
as fatty acid CoA ligase, but probably it’s something different -
sll6055, slr1505, slr1990 - all increased fitness in photoheterotrophic
condition, and decreased fitness in HC/LL conditions. Not much is known
about these genes, probably a role in photosynthesis, as the pattern is
similar to psb genes (PSII maturation?) - slr0813, slr0907, slr909,
slr1299 - all increased fitness in HC/LL. Not clear what connects these
genes functionally.
Differential fitness of
non-coding RNAs (ncRNAs)
General trends
The first task to study ncRNAs is to generate a new data frame with
additional annotation for ncRNAs. Additional annotation tables were
exported from Geneious and are based on the publication from Mitschke et al., PNAS,
2010. According to this publication, ncRNAs are grouped into four
different (slightly overlapping) classes:
- non-coding regulatory RNAs (ncRNAs in strict sense) not associated
to a gene
- iTSS, internal TSS within a gene
- asRNA, regulatory anti-sense RNAs associated with a gene
- Not included: 5’UTRs, alternative transcription start sites (TSS)
associated to a gene
df_ncRNA <- df_main %>% filter(sgRNA_type == "ncRNA") %>%
# obtain number of sgRNAs per target
group_by(sgRNA_target) %>%
summarize(sgRNA_number = length(unique(sgRNA_position))) %>%
# merge with df_gene table
inner_join(df_gene, by = "sgRNA_target") %>%
# generate ncRNA type based on target name
select(-locus, -gene_name, -sgRNA_type) %>%
mutate(sgRNA_target = str_sub(sgRNA_target, 4, 1000)) %>%
left_join(by = "sgRNA_target",
bind_rows(lapply(c("NC_000911_ncRNA.tsv", "NC_000911_asRNA.tsv", "NC_000911_iTSS.tsv"),
FUN = function(f) read_tsv(paste0("../data/input/", f), col_types = cols())
))
)
df_ncRNA %>% filter(time == 0) %>%
group_by(ncRNA_type, sgRNA_target) %>%
summarize(comb_score = any(comb_score >= 4), .groups = "drop_last") %>%
summarize(.groups = "drop",
n_targets = length(unique(sgRNA_target)),
`significant (score >= 4)` = sum(comb_score),
`non sign. (score < 4)` = sum(!comb_score),
`% sign.` = `significant (score >= 4)`/n_targets*100
)
Looking at the fitness and significance scores for one conditions, it
seems as internal transcription start sites are overrepresented in the
group that shows an effect. This is not a surprise, given that sgRNAs
targeting iTSS basically also repress the native gene as a regular
sgRNA. The library contains 1712 ncRNAs each targeted by 1 to 5 sgRNAs.
Only very few of those showed an effect on fitness. We can filter all
ncRNAs that have a “significance” equivalent to a fitness score abs(F)
>= 2 and -log10 p-value >= 2 (alpha = 0.01). Significance here
means effect size (F) multiplied by -log10 p-value, the threshold is
indicated by the dashed line.
plot_ncRNA_overview <- df_ncRNA %>%
filter(time == 0) %>%
mutate(ncRNA_type = factor(ncRNA_type, c("asRNA", "iTSS", "ncRNA"))) %>%
ggplot(aes(x = wmean_fitness, y = -log10(p_fitness_adj), color = ncRNA_type)) +
geom_point(alpha = 0.5, size = 0.5) +
geom_line(data = data.frame(x = c(seq(-8, -0.5, 0.1), seq(0.5, 8, 0.1)),
y = 4/c(seq(8, 0.5, -0.1), seq(0.5, 8, 0.1))),
aes(x = x, y = y, shape = NULL, col = NULL), lty = 2) +
coord_cartesian(xlim = c(-7, 7), ylim = c(0, 3)) +
custom_theme(aspect = 1, legend.position = "bottom") +
facet_wrap(~ condition, ncol = 6) +
labs(x = "fitness", y = expression("-log"[10]*" p-value")) +
scale_color_manual(values = custom_colors)
print(plot_ncRNA_overview)
Antisense RNAs and
iTSSs
The first part of a more detailed analysis is to extract asRNAs and
iTSSs with differential fitness, and compare them to their associated
genes. The assumption is that sgRNAs targeting asRNAs/iTSSs in reality
repress transcription of their parent genes, and by these means produce
a fitness effect that can not be attributed to the action of the asRNA
itself. The first step is filter the ncRNA dataset and order ncRNAs by
fitness similarity. The complete figure for the analysis follows at the
end of the chapter.
plot_ncRNA_overview <- df_ncRNA %>%
filter(time == 0) %>%
mutate(ncRNA_type = factor(ncRNA_type, c("asRNA", "iTSS", "ncRNA"))) %>%
ggplot(aes(x = wmean_fitness, y = -log10(p_fitness_adj), color = ncRNA_type)) +
geom_point(alpha = 0.5, size = 0.5) +
geom_line(data = data.frame(x = c(seq(-8, -0.5, 0.1), seq(0.5, 8, 0.1)),
y = 4/c(seq(8, 0.5, -0.1), seq(0.5, 8, 0.1))),
aes(x = x, y = y, shape = NULL, col = NULL), lty = 2) +
coord_cartesian(xlim = c(-7, 7), ylim = c(0, 3)) +
custom_theme(aspect = 1, legend.position = "bottom") +
facet_wrap(~ condition, ncol = 6) +
labs(x = "fitness", y = expression("-log"[10]*" p-value")) +
scale_color_manual(values = custom_colors)
print(plot_ncRNA_overview)

Plot asRNAs and iTSSs vs gene fitness.
df_ncRNA_select <- df_ncRNA %>%
filter(time == 0) %>%
group_by(sgRNA_target) %>%
filter(any(comb_score >= 4)) %>%
ungroup
plot_ncRNAs_xy <- lapply(c("asRNA", "iTSS"), FUN = function(type) {
df_ncRNA_select %>%
filter(ncRNA_type == type) %>%
left_join(by = c("condition", "locus"),
select(df_gene, locus, condition, wmean_fitness, sd_fitness) %>% distinct %>%
rename(gene_fitness = wmean_fitness, sd_gene_fitness = sd_fitness)) %>%
select(locus, condition, wmean_fitness, gene_fitness, comb_score) %>%
ggplot(aes(x = wmean_fitness, y = gene_fitness)) +
geom_abline(intercept = 0, slope = 1, lty = 2) +
geom_abline(intercept = 4, slope = 1, lty = 2) +
geom_abline(intercept = -4, slope = 1, lty = 2) +
geom_point(alpha = 0.5, size = 1.5, color = custom_colors[5]) +
ggpubr::stat_cor() +
coord_cartesian(xlim = c(-8, 5), ylim = c(-8, 5)) +
custom_theme(legend.pos = c(0.8, 0.15), legend.key.size = unit(0.2, "cm")) +
labs(x = paste0(type, " fitness"), y = "gene fitness")
})
noncoding RNAs as
regulatory elements
The second part of this analysis is to look at non-gene associated
(intergenic) ncRNA elements. Of these, several are known to have a
regulatory effect. We import an alignment that was done in
python using the biopython package. All ncRNAs
were aligned to the Synechocystis genome, and we can now
extract the genome positions to retrieve the neighboring genes.
The first step is to parse the (important) information of the
alignment text file into a table.
get_neighbors <- function(n, threshold, type = "logical",
lower_neighbor = TRUE, upper_neighbor = TRUE) {
n <- sort(n)
l <- rep_along(n, FALSE)
for (tr in threshold) {
if (tr > tail(n, 1) | tr < n[1])
stop("threshold exceeds range of input values")
if (upper_neighbor) {
l <- l | !duplicated(tr < n)}
if (lower_neighbor) {
l <- l | !duplicated(tr < n, fromLast = TRUE)}
if (!(tr >= n[1] & tr < n[2])) l[1] <- FALSE
if (!(tr > n[length(n)-1] & tr <= n[length(n)])){
l[length(n)] <- FALSE}
}
if (type == "logical") return(l)
else if (type == "pos") return(which(l))
else if (type == "value") return(n[l])
}
The second step is to look up the 5’ and 3’ neighboring genes for
each ncRNA. One could also do this manually but it becomes impractical
for something like 10+ ncRNA loci. After obtaining a table with both the
ncRNA in one column and its associated 5’ and 3’ neighboring genes in
separate columns, we can make a summary fitness table and plot fitness
of ncRNA and its neighbors against each other.
df_annotation <- read_csv("../../sgRNA_library/raw_data/Synechocystis_PCC6803_genome_annotation_20190614.csv", col_types = cols()) %>%
arrange(start_bp)
df_alignment <- read_lines("../data/output/ncRNA_alignment.txt") %>%
as_tibble %>%
mutate(name = rep(c("locus", "genome", "genome_length", "score", "seq", "alignment", "genome_pos", "blank"), length.out = n())) %>%
pivot_wider(names_from = "name", values_from = "value") %>%
tidyr::unchop(cols = everything()) %>%
mutate(locus = str_remove(locus, "alignment of: ")) %>%
filter(str_detect(genome, "47118304")) %>%
select(-genome_length, -score, -seq, -alignment, -blank) %>%
mutate(
genome_start = str_extract(genome_pos, "Sbjct: +[0-9]*") %>%
str_extract("[0-9]+") %>% as.numeric) %>%
mutate(
genome_end = str_extract(genome_pos, "[ATCG] +[0-9]*") %>%
str_extract("[0-9]+") %>% as.numeric) %>%
mutate(length = abs(genome_end-genome_start))
Warning: Values from `value` are not uniquely identified; output will contain list-cols.
* Use `values_fn = list` to suppress this warning.
* Use `values_fn = {summary_fun}` to summarise duplicates.
* Use the following dplyr code to identify duplicates.
{data} %>%
dplyr::group_by(name) %>%
dplyr::summarise(n = dplyr::n(), .groups = "drop") %>%
dplyr::filter(n > 1L)
head(df_alignment)
The third step is to plot a heat map and the comparison of upstream
and downstream gene fitness.
# get neighboring genes
df_alignment <- df_alignment %>%
bind_cols(
sapply(df_alignment$genome_start, function(x) {
df = filter(df_annotation, location == "Chr")
neighbors = get_neighbors(n = df$start_bp, threshold = x, type = "pos")
unlist(as.list(df[neighbors, "GeneID"]))
}) %>% t %>% as_tibble
)
# some join operations to merge different fitness data in one DF
df_ncRNA_comp <- df_ncRNA_select %>% filter(ncRNA_type == "ncRNA") %>%
left_join(rename(df_alignment, sgRNA_target = locus, upstream = GeneID1, downstream = GeneID2) %>%
select(sgRNA_target, upstream, downstream),
by = "sgRNA_target") %>%
left_join(df_gene %>% select(locus, condition, wmean_fitness) %>%
distinct %>% rename(upstream = locus, upstream_gene_fitness = wmean_fitness),
by = c("condition", "upstream")
) %>%
left_join(df_gene %>% select(locus, condition, wmean_fitness) %>%
distinct %>% rename(downstream = locus, downstream_gene_fitness = wmean_fitness),
by = c("condition", "downstream")
)
# get a list of interesting ncRNAs where the neighboring gene
# has NO effect on fitness
list_ncRNA_hits <- df_ncRNA_comp %>%
filter(
abs(wmean_fitness - upstream_gene_fitness) >= 4 &
abs(wmean_fitness - downstream_gene_fitness) >= 4
) %>% group_by(sgRNA_target) %>% count %>%
filter(n > 1) %>%
pull(sgRNA_target)
Main text figure for ncRNAs only.
plot_ncRNA_heat <- df_ncRNA_select %>% filter(ncRNA_type == "ncRNA") %>%
mutate(sgRNA_target = fct_cluster(sgRNA_target, condition, wmean_fitness)) %>%
mutate(wmean_fitness = wmean_fitness %>% replace(., . > 4, 4) %>% replace(., . < -4, -4)) %>%
ggplot(aes(x = condition, y = sgRNA_target, fill = wmean_fitness)) +
geom_tile() +
custom_theme(legend.pos = "bottom",
legend.key.size = unit(0.4, "cm"), legend.margin = unit(0, "cm")) +
labs(x = "", y = "") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
scale_fill_gradientn(colours = c(custom_colors[1], grey(0.9), custom_colors[2]),
limits = c(-4, 4))
Warning: `legend.margin` must be specified using `margin()`. For the old behavior use legend.spacing
# check correlation of ncRNA fitness with upstream and downstream lying genes
plot_ncRNA_upstream <-
lapply(c("upstream_gene_fitness", "downstream_gene_fitness"), FUN = function(var){
df_ncRNA_comp %>%
mutate(sgRNA_target = if_else(locus %in% list_ncRNA_hits, sgRNA_target, "other")) %>%
arrange(desc(sgRNA_target), wmean_fitness) %>%
ggplot(aes(x = wmean_fitness, y = get(var), color = sgRNA_target)) +
geom_abline(intercept = 0, slope = 1, lty = 2) +
geom_abline(intercept = 4, slope = 1, lty = 2) +
geom_abline(intercept = -4, slope = 1, lty = 2) +
geom_point(alpha = 0.5, size = 1.5) +
ggpubr::stat_cor(aes(x = wmean_fitness, y = get(var), color = NULL)) +
coord_cartesian(xlim = c(-8, 5), ylim = c(-8, 5)) +
custom_theme(legend.pos = c(0.8, 0.2), legend.key.size = unit(0.2, "cm")) +
labs(x = "ncRNA fitness", y = str_replace_all(var, "_", " ")) +
scale_color_manual(values = custom_colors[c(3,4,5)])
})
null device
1
Experimental
validation of targets
Growth
characteristics of WT, CP12, GAP1 and GAP2 mutants
- we found that CP12 protein
ssl3364 shows interesting
phenotype for glucose-supplemented growth
- CP12 KD mutant was more sensitive when grown on +G or +G+D
- we also found that gap1 (
slr0884) and gap2
(sll1342) KD mutants show different phenotype compared to
previous reports
- Gap1 mutant had no phenotype although it was reported to
df_ncRNA_select %>% filter(ncRNA_type == "ncRNA") %>%
select(!matches("time|log2Fold")) %>%
write_csv("../data/output/fitness_ncRNA.csv")
- read in validation data
- 3 tables with OD measurement of the described strains and
conditions´
df_gene %>% filter(locus %in% c("ssl3364", "slr0884", "sll1342")) %>%
mutate(sgRNA_target = recode(sgRNA_target, `ssl3364`= "CP12")) %>%
plot_gene_fitness(ncol = 6, legend.position = "bottom") +
coord_cartesian(ylim = c(-7.5, 2.9))

- plot the data using custom function
- each in its own field, because conditions and mutants are all
different
df_od <- lapply(
list.files("../data/input/validation/", pattern = "\\_OD.csv", full.names = TRUE),
function(f) {
read_csv(f, show_col_types = FALSE) %>%
rename_with(.fn = function(x) str_replace(x, "batchtime_h|hours", "time_h")) %>%
pivot_longer(-time_h, names_to = "condition", values_to = "OD720") %>%
mutate(condition = str_remove_all(condition, "[\\.0-9]*$")) %>%
group_by(condition, time_h) %>% mutate(replicate = seq_along(OD720)) %>%
separate(condition, sep = "\\_", into = c("mutant", "condition"))
}
) %>% setNames(c("ΔCP12", "ΔGAP1", "ΔGAP2")) %>%
bind_rows(.id = "comparison") %>%
mutate(mutant = recode(mutant, `Δssl3364` = "ΔCP12"))
# optionally simplify names of conditions
df_od <- df_od %>%
mutate(condition = case_when(
str_detect(condition, "\\+G, \\+D$") ~ "hetero",
str_detect(condition, "\\+G$") ~ "mixo",
str_detect(condition, "[IHL]{2}$") ~ "photo"
))
- create a summary table with growthrates
- calculate maximum growth rate and a corresponding plot
df_od_summary <- df_od %>%
group_by(time_h, comparison, condition, mutant) %>%
summarize(
mean_OD720 = mean(OD720),
sd_OD720 = sd(OD720),
ymin = mean_OD720 - sd_OD720,
ymax = mean_OD720 + sd_OD720
)
print_od <- function(data, log = FALSE, ylim = NULL) {
if (log) {
data <- mutate(data, across(matches("mean|ymin|ymax"), ~ log(.x)))
}
data %>% ungroup %>%
mutate(
condition = paste0(condition, " ", mutant),
condition = factor(condition, unique(condition)[c(3,4,1,2)])
) %>%
ggplot(aes(x = time_h, y = mean_OD720,
color = condition,
fill = condition,
ymin = ymin, ymax = ymax)) +
geom_ribbon(alpha = 0.2, linetype = 0) +
geom_line(size = 1) +
custom_theme(legend.pos = c(0.35, 0.85), legend.key.size = unit(0.15, "cm")) +
labs(x = "time [h]", y = expression("OD"[720])) +
coord_cartesian(ylim = ylim) +
facet_wrap( ~ comparison) +
scale_y_continuous(labels = function(x) format(x, nsmall = 2)) +
scale_color_manual(values = c("#66A61E", "#b4d889", "#E7298A", "#eb97c4")) +
scale_fill_manual(values = c( "#66A61E", "#b4d889", "#E7298A", "#eb97c4"))
}
df_mu <- df_od %>%
filter(time_h >= 20 & time_h <= 40) %>%
group_by(comparison, condition, mutant, replicate) %>%
summarize(mu_max = lm(log(OD720) ~ time_h)$coefficients[2]) %>%
# calculate t-test p-value
group_by(comparison, condition) %>%
arrange(comparison, condition, mutant) %>%
mutate(p_value = t.test(x = mu_max[mutant != "WT"], y = mu_max[mutant == "WT"])$p.value) %>%
group_by(comparison, condition, mutant) %>%
# summary statistics
summarize(
mean_mu_max = mean(mu_max),
sd_mu_max = sd(mu_max),
ymin = mean_mu_max - sd_mu_max,
ymax = mean_mu_max + sd_mu_max,
p_value = p_value[1]
) %>%
mutate(p_value_symbol = if_else(p_value <= 0.01 & mutant != "WT", "*", ""))
`summarise()` has grouped output by 'comparison', 'condition', 'mutant'. You can override using the `.groups` argument.`summarise()` has grouped output by 'comparison', 'condition'. You can override using the `.groups` argument.
- specific plot for small fitness score figures
- only the 2 selected conditions from growth data
print_mu <- function(data) {
data %>% ungroup %>%
mutate(
condition = paste0(condition, " ", mutant),
condition = factor(condition, unique(condition)[c(3,4,1,2)])
) %>%
ggplot(aes(x = condition, y = mean_mu_max,
color = condition,
fill = condition,
ymin = ymin, ymax = ymax)) +
geom_col(position = "dodge", width = 0.6) +
geom_errorbar(position = "dodge", width = 0.6, size = 1) +
geom_text(aes(label = p_value_symbol), size = 8, nudge_y = 0.003) +
custom_theme(legend.position = 0) +
labs(x = "", y = expression("µ [h"^-1*"]")) +
theme(axis.text.x = element_text(angle = 35, vjust = 1, hjust = 1)) +
coord_cartesian(ylim = c(0,0.05)) +
facet_wrap( ~ comparison) +
scale_color_manual(values = c("#66A61E", "#b4d889", "#E7298A", "#eb97c4")) +
scale_fill_manual(values = c("#66A61E", "#b4d889", "#E7298A", "#eb97c4"))
}
- plot everything on a large canvas
- this will become the validation figure
print_fitness <- function(data) {
data %>%
select(gene_name, condition, wmean_fitness, sd_fitness) %>%
distinct %>%
ggplot(aes(x = condition, y = wmean_fitness,
color = condition, fill = condition,
ymin = wmean_fitness - sd_fitness,
ymax = wmean_fitness + sd_fitness)) +
geom_col(position = "dodge", width = 0.6) +
geom_errorbar(position = "dodge", width = 0.6, size = 1) +
custom_theme(legend.position = 0) +
labs(x = "", y = "fitness") +
theme(axis.text.x = element_text(angle = 35, vjust = 1, hjust = 1)) +
coord_cartesian(ylim = c(-6, 4)) +
facet_wrap( ~ gene_name) +
scale_color_manual(values = c("#b4d889", "#eb97c4")) +
scale_fill_manual(values = c("#b4d889", "#eb97c4"))
}

Export summary table
of all genes and conditions
Export a summary table of all genes and conditions, so that it’s easy
for other people to look up single conditions as for example done in one-by-one fitness
comparisons. This is best done in wide format (one column per
condition).
svg("../figures/figure3_validation.svg", width = 9, height = 9)
ggarrange(ncol = 3, nrow = 3,
print_od(filter(df_od_summary, comparison == "ΔGAP1"), ylim = c(0, 1.6)),
print_od(filter(df_od_summary, comparison == "ΔGAP2"), ylim = c(0, 1.0)),
print_od(filter(df_od_summary, comparison == "ΔCP12"), ylim = c(0, 1.0)),
print_od(filter(df_od_summary, comparison == "ΔGAP1"), ylim = c(-2.5, 0.5), log = TRUE)
+ theme(plot.margin = unit(c(12, 12, 12, 8),"points")),
print_od(filter(df_od_summary, comparison == "ΔGAP2"), ylim = c(-2.5, 0.5), log = TRUE)
+ theme(plot.margin = unit(c(12, 12, 12, 8),"points")),
print_od(filter(df_od_summary, comparison == "ΔCP12"), ylim = c(-2.5, 0.5), log = TRUE)
+ theme(plot.margin = unit(c(12, 12, 12, 8),"points")),
ggarrange(ncol = 2, widths = c(0.65, 0.35),
print_mu(filter(df_mu, comparison == "ΔGAP1")),
print_fitness(filter(df_gene, locus == "slr0884", condition %in% c("HC, LL", "HC, LL, +G")))
+ theme(plot.margin = unit(c(12, 12, 19, -4),"points"))
),
ggarrange(ncol = 2, widths = c(0.65, 0.35),
print_mu(filter(df_mu, comparison == "ΔGAP2"))
+ theme(plot.margin = unit(c(12, 12, 10, 12),"points")),
print_fitness(filter(df_gene, locus == "sll1342", condition %in% c("LC, IL", "LC, LL, +D, +G")))
+ theme(plot.margin = unit(c(12, 12, 10, -4),"points"))
),
ggarrange(ncol = 2, widths = c(0.65, 0.35),
print_mu(filter(df_mu, comparison == "ΔCP12"))
+ theme(plot.margin = unit(c(12, 12, 11, 8),"points")),
print_fitness(filter(df_gene, locus == "ssl3364", condition %in% c("LC, LL", "LC, LL, +D, +G")))
+ theme(plot.margin = unit(c(12, 12, 10, -4),"points"))
)
)
dev.off()
null device
1
The entire pipeline takes about 5 minutes to run on a standard
notebook. To work on single sections, the work space is exported to
avoid constant recalculation of result tables.
LS0tCnRpdGxlOiAiQ1JJU1BSaSBsaWJyYXJ5IFYyLCBkYXRhIHByb2Nlc3NpbmcgcGlwZWxpbmUiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IGNvc21vCiAgICB0b2M6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCi0tLS0tLS0tLS0KCiMgRGVzY3JpcHRpb24KClRoaXMgUiBub3RlYm9vayBkZXRhaWxzIHRoZSBkYXRhIHByb2Nlc3NpbmcgYW5kIHZpc3VhbGl6YXRpb24gZm9yIGdyb3d0aCBjb21wZXRpdGlvbiBleHBlcmltZW50cyB3aXRoIGEgQ1JJU1BSaSBzZ1JOQSBsaWJyYXJ5LiBUaGUgbGlicmFyeSBjb250YWlucyBhcm91bmQgMjAsMDAwIHVuaXF1ZSBzZ1JOQSByZXByZXNzaW9uIG11dGFudHMgdGFpbG9yZWQgZm9yIHRoZSBjeWFub2JhY3Rlcml1bSBfU3luZWNob2N5c3Rpc18gc3AuIFBDQzY4MDMuIFRoaXMgbGlicmFyeSBpcyB0aGUgc2Vjb25kIHZlcnNpb24gKHRoZXJlZm9yZSAiVjIiKSBvZiBhbiBzZ1JOQSBsaWJyYXJ5IGZvciBfU3luZWNob2N5c3Rpc18sIGNvbnRhaW5pbmcgZml2ZSBpbnN0ZWFkIG9mIG9ubHkgdHdvIHNnUk5BcyBwZXIgZ2VuZS4gSW4gc29tZSBjYXNlcywgZ2VuZXMgb3IgbmNSTkFzIGFyZSBzbyBzaG9ydCB0aGF0IGl0IGlzIG5vdCBwb3NzaWJsZSB0byBkZXNpZ24gYSBtYXhpbXVtIG9mIGZpdmUgaW5kaXZpZHVhbCBzZ1JOQXMuCgpUaGUgZmlyc3QgaXRlcmF0aW9uIG9mIHRoZSBfU3luZWNob2N5c3Rpc18gc2dSTkEgbGlicmFyeSB3YXMgW3B1Ymxpc2hlZCBpbiBOYXR1cmUgQ29tbXVuaWNhdGlvbnMsIDIwMjBdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAyMC0xNTQ5MS03KS4KCiMgUHJlcmVxdWlzaXRlcwoKTG9hZCByZXF1aXJlZCBwYWNrYWdlcy4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UgfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkoZ2dyZXBlbCkKICBsaWJyYXJ5KGxhdHRpY2UpCiAgbGlicmFyeShsYXR0aWNlRXh0cmEpCiAgbGlicmFyeShsYXR0aWNldG9vbHMpCiAgbGlicmFyeShzY2FsZXMpCiAgbGlicmFyeShkZW5kZXh0ZW5kKQogIGxpYnJhcnkodmVnYW4pCiAgbGlicmFyeSh0c25lKQogIGxpYnJhcnkoS0VHR1JFU1QpCiAgbGlicmFyeShsaW1tYSkKICBsaWJyYXJ5KGNvcnJwbG90KQogIGxpYnJhcnkoa2FibGVFeHRyYSkKICBsaWJyYXJ5KGdyaWQpCiAgbGlicmFyeShnZ3B1YnIpCiAgbGlicmFyeShSdG9vbHMpCn0pCmBgYAoKRGVmaW5lIGdsb2JhbCBmaWd1cmUgc3R5bGUsIGRlZmF1bHQgY29sb3JzLCBhbmQgYSBwbG90IHNhdmluZyBmdW5jdGlvbi4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CiMgY3VzdG9tIGdncGxvdDIgdGhlbWUgdGhhdCBpcyByZXVzZWQgZm9yIGFsbCBsYXRlciBwbG90cwpjdXN0b21fY29sb3JzID0gYygiI0U3Mjk4QSIsICIjNjZBNjFFIiwgIiNFNkFCMDIiLCAiIzc1NzBCMyIsICIjQjNCM0IzIiwgIiMxQjlFNzciLCAiI0Q5NUYwMiIsICIjQTY3NjFEIikKY3VzdG9tX3JhbmdlIDwtIGZ1bmN0aW9uKG4gPSA1KSB7Y29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzW2MoMSw1LDIpXSkobil9CgpjdXN0b21fdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gMTIsIGJhc2VfbGluZV9zaXplID0gMS4wLCBiYXNlX3JlY3Rfc2l6ZSA9IDEuMCwgLi4uKSB7CiAgdGhlbWVfbGlnaHQoYmFzZV9zaXplID0gYmFzZV9zaXplLCBiYXNlX2xpbmVfc2l6ZSA9IGJhc2VfbGluZV9zaXplLCBiYXNlX3JlY3Rfc2l6ZSA9IGJhc2VfcmVjdF9zaXplKSArIHRoZW1lKAogICAgdGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3VyID0gZ3JleSgwLjQpLCBzaXplID0gMTApLAogICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsMTIsMTIsMTIpLCAicG9pbnRzIiksCiAgICBheGlzLnRpY2tzLmxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gZ3JleSgwLjQpLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGxpbmVlbmQgPSAicm91bmQiKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNiwgbGluZXR5cGUgPSAic29saWQiLCBjb2xvdXIgPSBncmV5KDAuOSkpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG91ciA9IGdyZXkoMC40KSwgZmlsbCA9IE5BLCBzaXplID0gMS4wKSwKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSBncmV5KDAuNCksIHNpemUgPSAxMCwgbWFyZ2luID0gdW5pdChyZXAoMyw0KSwgInBvaW50cyIpKSwKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IGdyZXkoMC40KSwgc2l6ZSA9IDEwKSwKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgLi4uCiAgKQp9CgojIHNldCBncmFwaGljYWwgcGFyYW1ldGVyIGZvciBzdWJmaWd1cmUgbGFiZWxzCmxpc3RfZm9udHBhcnMgPC0gbGlzdChmYWNlID0gInBsYWluIiwgc2l6ZSA9IDE0KQoKIyBmdW5jdGlvbiB0byBleHBvcnQgYW4gaW1hZ2UgYXMgc3ZnIGFuZCBwbmcKc2F2ZV9wbG90IDwtIGZ1bmN0aW9uKHBsLCBwYXRoID0gIi4uL2ZpZ3VyZXMvIiwgd2lkdGggPSA2LCBoZWlnaHQgPSA2KSB7CiAgcGxfbmFtZSA8LSBkZXBhcnNlKHN1YnN0aXR1dGUocGwpKQogIHN2ZyhmaWxlbmFtZSA9IHBhc3RlMChwYXRoLCBwbF9uYW1lLCAiLnN2ZyIpLAogICAgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0KQogIHByaW50KHBsKQogIGRldi5vZmYoKQogIHBuZyhmaWxlbmFtZSA9IHBhc3RlMChwYXRoLCBwbF9uYW1lLCAiLnBuZyIpLAogICAgd2lkdGggPSB3aWR0aCoxMjUsIGhlaWdodCA9IGhlaWdodCoxMjUsIHJlcyA9IDEyMCkKICBwcmludChwbCkKICBpbnZpc2libGUoY2FwdHVyZS5vdXRwdXQoZGV2Lm9mZigpKSkKfQpgYGAKCgojIFF1YWxpdHkgY29udHJvbAoKIyMgRGF0YSBpbXBvcnQKCkxvYWQgcmF3IGRhdGEuIFRoZSBtYWluIHRhYmxlIGNvbnRhaW5zIGFscmVhZHkgbm9ybWFsaXplZCBxdWFudGlmaWNhdGlvbiBvZiBhbGwgc2dSTkFzLCBmb2xkIGNoYW5nZSwgbXVsdGlwbGUgaHlwb3RoZXNpcyBjb3JyZWN0ZWQgcC12YWx1ZXMsIGFuZCBmaXRuZXNzIHNjb3JlcyBvbiB0aGUgZ3VpZGUgUk5BIGFuZCBnZW5lIGxldmVsLiBDb250cmFyeSB0byB0aGUgcHJvY2Vzc2luZyBvZiBbb3VyIGZpcnN0IENSSVNQUmkgbGlicmFyeSBWMV0oaHR0cHM6Ly9naXRodWIuY29tL20tamFobi9SLW5vdGVib29rLWNyaXNwcmktbGliI2NvbnRlbnRzKSwgbXVjaCBvZiB0aGUgZnVuY3Rpb25hbGl0eSBmcm9tIHRoZSBub3RlYm9vayB3YXMgdHJhbnNmZXJyZWQgaW50byBhIG5ldyBbTkV4dGZsb3cgQ1JJU1BSaSBsaWJyYXJ5IHBpcGVsaW5lXShodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL25mLWNvcmUtY3Jpc3ByaXNjcmVlbikgYXZhaWxhYmxlIG9uIGdpdGh1Yi4KCmBgYHtyfQpsb2FkKCIuLi9kYXRhL2lucHV0L3Jlc3VsdC5SZGF0YSIpCmRmX21haW4gPC0gREVTZXFfcmVzdWx0X3RhYmxlCnJtKERFU2VxX3Jlc3VsdF90YWJsZSkKYGBgCgoKIyMgRGF0YSBhbm5vdGF0aW9uCgpEaWZmZXJlbnQgYW5ub3RhdGlvbiBjb2x1bW5zIGFyZSBhZGRlZCB0byB0aGUgbWFpbiBkYXRhIGZyYW1lLCBpbmNsdWRpbmcgYSBzaG9ydCBzZ1JOQSBpZGVudGlmaWVyIChleGNsdWRpbmcgdGhlIHBvc2l0aW9uIG9uIHRoZSBnZW5lKSwgYW4gc2dSTkEgaW5kZXggKDEgdG8gNSksIGFuZCBnZW5vbWUgYW5ub3RhdGlvbiBmcm9tIFVuaXByb3QuIFRoZSBVbmlwcm90IGRhdGEgaXMgZHluYW1pY2FsbHkgZG93bmxvYWRlZCBmb3IgZXZlcnkgdXBkYXRlIG9mIHRoaXMgcGlwZWxpbmUgdXNpbmcgdGhlaXIgdmVyeSBzaW1wbGUgQVBJIChgcmVhZF90c3YoImh0dHBzOi8vd3d3LnVuaXByb3Qub3JnL3VuaXByb3QvP3F1ZXJ5PXRheG9ub215OjExMTE3MDgmZm9ybWF0PXRhYiIpYCkuIFRoZSBmdWxsIGxpc3Qgb2YgY29sdW1ucyB0aGF0IGNhbiBiZSBxdWVyaWVkIGlzIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvaGVscC91bmlwcm90a2JfY29sdW1uX25hbWVzKS4KUGF0aHdheSBhbm5vdGF0aW9uIGZyb20gS0VHRyBpcyBsYXRlciBpbiB0aGUgcGlwZWxpbmUgYWRkZWQgdXNpbmcgdGhlIGBLRUdHUkVTVGAgcGFja2FnZS4KCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX21haW4gPC0gZGZfbWFpbiAlPiUKICBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIG11dGF0ZShzZ1JOQV90eXBlID0gaWZfZWxzZShncmVwbCgiXm5jXyIsIHNnUk5BKSwgIm5jUk5BIiwgImdlbmUiKSkgJT4lCiAgdW5ncm91cCAlPiUKICAKICAjIG1hcCB0cml2aWFsIG5hbWVzIHRvIExvY3VzVGFncyB1c2luZyBhIG1hbnVhbGx5IGN1cmF0ZWQgbGlzdAogIGxlZnRfam9pbigKICAgIHJlYWRfdHN2KCIuLi9kYXRhL2lucHV0L21hcHBpbmdfdHJpdmlhbF9uYW1lcy50c3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpLAogICAgYnkgPSBjKCJzZ1JOQV90YXJnZXQiID0gImdlbmUiKSkgJT4lCgogICMgc3BsaXQgY29uZGl0aW9uIGludG8gc2VwYXJhdGUgY29scwogIHNlcGFyYXRlKGNvbmRpdGlvbiwgaW50byA9IGMoImNhcmJvbiIsICJsaWdodCIsICJ0cmVhdG1lbnRfMSIsICJ0cmVhdG1lbnRfMiIpLAogICAgc2VwID0gIiwgIiwgcmVtb3ZlID0gRkFMU0UsIGZpbGwgPSAicmlnaHQiKSAlPiUKICB1bml0ZSgidHJlYXRtZW50IiwgdHJlYXRtZW50XzEsIHRyZWF0bWVudF8yLCBzZXAgPSAiLCAiLCBuYS5ybSA9IFRSVUUpCmBgYAoKT3ZlcnZpZXcgYWJvdXQgdGhlIGRpZmZlcmVudCBjb25kaXRpb25zLgoKYGBge3J9CmRmX2N1bHRpdmF0aW9uX3N1bW1hcnkgPC0gZGZfbWFpbiAlPiUgZ3JvdXBfYnkoY29uZGl0aW9uKSAlPiUKICBzdW1tYXJpemUoCiAgICB0aW1lX3BvaW50cyA9IHBhc3RlKHVuaXF1ZSh0aW1lKSwgY29sbGFwc2UgPSAiLCAiKSwKICAgIGNhcmJvbiA9IHVuaXF1ZShjYXJib24pLAogICAgbGlnaHQgPSB1bmlxdWUobGlnaHQpLAogICAgdHJlYXRtZW50ID0gdW5pcXVlKHRyZWF0bWVudCksCiAgICBtaW5fZml0ID0gbWluKGZpdG5lc3MpLAogICAgbWVkX2ZpdCA9IG1lZGlhbihmaXRuZXNzKSwKICAgIG1heF9maXQgPSBtYXgoZml0bmVzcykpCgpwcmludChkZl9jdWx0aXZhdGlvbl9zdW1tYXJ5KQp3cml0ZV9jc3YoZGZfY3VsdGl2YXRpb25fc3VtbWFyeSwgZmlsZSA9ICIuLi9kYXRhL291dHB1dC9jdWx0aXZhdGlvbl9zdW1tYXJ5LmNzdiIpCmBgYAoKUmV0cmlldmUgZ2VuZSBpbmZvIGZyb20gdW5pcHJvdCBhbmQgbWVyZ2Ugd2l0aCBtYWluIGRhdGEgZnJhbWUuIFdlIG5lZWQgdG8gbWFrZSBhIGN1c3RvbSBmdW5jdGlvbiB0byByZXRyaWV2ZSBhbmQgcGFyc2UgdGhlIGRhdGEgZnJvbSB1bmlwcm90LCBiZWNhdXNlIG9mIGEgYnVnIGluIHRoZSBzZWN1cml0eSBsZXZlbCBvbiBVYnVudHUgMjAuMDQuIFRoZSBmYWxsYmFjayBvcHRpb24gaXMgdG8gbG9hZCBhIGxvY2FsIGNvcHkgb2YgdW5pcHJvdCBhbm5vdGF0aW9uIGZvciB0aGlzIG9yZ2FuaXNtLgoKYGBge3J9CnVuaXByb3RfdXJsIDwtIHBhc3RlMCgKICAiaHR0cHM6Ly9yZXN0LnVuaXByb3Qub3JnL3VuaXByb3RrYi9zdHJlYW0/ZmllbGRzPWFjY2Vzc2lvbiIsCiAgIiUyQ2lkJTJDcHJvdGVpbl9uYW1lJTJDZ2VuZV9uYW1lcyUyQ29yZ2FuaXNtX25hbWUlMkNsZW5ndGgiLAogICIlMkNnZW5lX29sbiUyQ2dlbmVfcHJpbWFyeSUyQ2NjX3BhdGh3YXklMkNnb19pZCUyQ2NjX3N1YmNlIiwKICAibGx1bGFyX2xvY2F0aW9uJTJDY2NfaW50ZXJhY3Rpb24lMkN4cmVmX2tlZ2cmZm9ybWF0PXRzdiZxdWVyeT0lMjhwcm90ZSIsCiAgIm9tZSUzQVVQMDAwMDAxNDI1JTI5IgopCgpnZXRfdW5pcHJvdCA8LSBmdW5jdGlvbih1cmwpIHsKICBkZl91bmlwcm90IDwtIHRyeUNhdGNoKHsKICAgICAgcmVhZF90c3YodW5pcHJvdF91cmwsCiAgICAgICAgY29sX3R5cGVzID0gY29scygpLAogICAgICAgIG5hbWVfcmVwYWlyID0gZnVuY3Rpb24oeCl7c3RyX3JlcGxhY2VfYWxsKHRvbG93ZXIoeCksICJbIFxcLl0iLCAiXyIpfSkKICAgIH0sCiAgICBlcnJvciA9IGZ1bmN0aW9uKHNlcnZlcl9lcnJvcikgewogICAgICBtZXNzYWdlKCJVbmlwcm90IHNlcnZlciBub3QgYXZhaWxhYmxlLCBmYWxsaW5nIGJhY2sgb24gbG9jYWwgVW5pcHJvdCBEQiBjb3B5IikKICAgICAgcmVhZF90c3YoIi4uL2RhdGEvaW5wdXQvdW5pcHJvdF9zeW5lY2hvY3lzdGlzLnRzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkKICAgIH0KICApCn0KCmRmX3VuaXByb3QgPC0gZ2V0X3VuaXByb3QodW5pcHJvdF91cmwpICU+JQogIHJlbmFtZShsb2N1cyA9IGBrZWdnYCwgZ2VuZV9uYW1lID0gYGdlbmVfbmFtZXNgLAogICAgZ2VuZV9uYW1lX3Nob3J0ID0gYGdlbmVfbmFtZXNfKHByaW1hcnkpYCwgcHJvdGVpbiA9IGBwcm90ZWluX25hbWVzYCwKICAgIHVuaXByb3RfaWQgPSBgZW50cnlgCiAgKSAlPiUKICBtdXRhdGUobG9jdXMgPSBzdHJfcmVtb3ZlX2FsbChsb2N1cywgInN5bjp8OyQiKSkgJT4lCiAgc2VwYXJhdGVfcm93cyhsb2N1cywgZ2VuZV9uYW1lX3Nob3J0LCBzZXAgPSAiOyIpICU+JQogIGZpbHRlcighaXMubmEobG9jdXMpLCAhZHVwbGljYXRlZChsb2N1cykpCgpkZl9tYWluIDwtIGxlZnRfam9pbigKICBkZl9tYWluLCBkZl91bmlwcm90ICU+JSBzZWxlY3QoLWBnZW5lX25hbWVzXyhvcmRlcmVkX2xvY3VzKWAsCiAgICAtYHBhdGh3YXlgLCAtYGdlbmVfb250b2xvZ3lfaWRzYCwgLWBpbnRlcmFjdHNfd2l0aGApLAogIGJ5ID0gImxvY3VzIikKYGBgCgojIyBOdW1iZXIgb2Ygc2dSTkFzCgpFYWNoIGdlbmUgaXMgcmVwcmVzZW50ZWQgYnkgdXAgdG8gZml2ZSBzZ1JOQXMuIFdlIGNhbiB0ZXN0IGlmIGFsbCBvciBvbmx5IHNvbWUgb2YgdGhlIDUgc2dSTkFzIGFyZSAiYmVoYXZpbmciIGluIHRoZSBzYW1lIHdheSBpbiB0aGUgc2FtZSBjb25kaXRpb25zLCBtb3JlIG1hdGhlbWF0aWNhbGx5IHNwZWFraW5nIHdlIGNhbiBlc3RpbWF0ZSB0aGUgY29ycmVsYXRpb24gb2YgZXZlcnkgc2dSTkEgd2l0aCBhbm90aGVyLiBGaXJzdCBsZXQncyBzdW1tYXJpemUgaG93IG1hbnkgZ2VuZXMgaGF2ZSA1LCA0LCAzIHNnUk5BcyBhbmQgc28gb24gYXNzb2NpYXRlZCB3aXRoIHRoZW0uCgpgYGB7ciwgLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gMy41fQojIE4gdW5pcXVlIHNnUk5BcyBpbiBkYXRhc2V0CnBhc3RlMCgiTnVtYmVyIG9mIHVuaXF1ZSBzZ1JOQXM6ICIsIHVuaXF1ZShkZl9tYWluJHNnUk5BKSAlPiUgbGVuZ3RoKQoKIyBOIGdlbmVzIHdpdGggMSwyLDMsNCBvciA1IHNnUk5BcwpwbG90X3NnUk5Bc19wZXJfZ2VuZSA8LSBkZl9tYWluICU+JQogIGdyb3VwX2J5KHNnUk5BX3R5cGUsIHNnUk5BX3RhcmdldCkgJT4lCiAgc3VtbWFyaXplKG5fc2dSTkFzID0gbGVuZ3RoKHVuaXF1ZShzZ1JOQV9wb3NpdGlvbikpLCAuZ3JvdXBzID0gImRyb3BfbGFzdCIpICU+JQogIGNvdW50KG5fc2dSTkFzKSAlPiUgZmlsdGVyKG5fc2dSTkFzIDw9IDUpICU+JQogIGdncGxvdChhZXMoeCA9IGZhY3RvcihuX3NnUk5BcywgNToxKSwgeSA9IG4sIGxhYmVsID0gbikpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV90ZXh0KHNpemUgPSAzLCBudWRnZV95ID0gMjAwLCBjb2xvciA9IGdyZXkoMC41KSkgKwogIGZhY2V0X2dyaWQofiBzZ1JOQV90eXBlKSArCiAgbGFicyh4ID0gInNnUk5BcyAvIHRhcmdldCIsIHkgPSAidGFyZ2V0cyIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTUwLCAzNTAwKSkgKwogIGN1c3RvbV90aGVtZSgpCgpwcmludChwbG90X3NnUk5Bc19wZXJfZ2VuZSkKYGBgCgojIyBGaXRuZXNzIGRpc3RyaWJ1dGlvbiBvZiBhbGwgY29uZGl0aW9ucwoKQmVmb3JlIGJpb2xvZ2ljYWwgYW5hbHlzaXMgY29udGludWVzLCB3ZSBuZWVkIHRvIGNoZWNrIGlmIGZpdG5lc3MgKGFuZCBsb2cyIEZDIGZyb20gd2hpY2ggaXQgaXMgY2FsY3VsYXRlZCkgaXMgZXF1YWxseSBkaXN0cmlidXRlZC4gRm9yIGV4YW1wbGUsIHN0cmljdGx5IGVzc2VudGlhbCBnZW5lcyBsaWtlIHJpYm9zb21hbCBnZW5lcyBzaG91bGQgc2hvdyB0aGUgc2FtZSBkZWdyZWVlIG9mIGRlcGxldGlvbiBvdmVyIHRpbWUsIHJlZ2FyZGxlc3Mgb2YgY29uZGl0aW9uLgoKV2UgY2FuIGNvbXBhcmUgZml0bmVzcyBvdmVyIGFsbCBjb25kaXRpb25zIHVzaW5nIGEgc2NhdHRlciBwbG90IG1hdHJpeC4gV2UgY2FuIHNlZSB0aGF0IHNvbWUgY29uZGl0aW9ucyBhcmUgdmVyeSBzaW1pbGFyIHRvIGVhY2ggb3RoZXIsIGZvciBleGFtcGxlIHRoZSBjb25kaXRpb25zIHRyZWF0ZWQgd2l0aCBnbHVjb3NlIChgTEMsIExMICtnYCwgYExDLCBMTCwgK0QsICtHYCwgYEhDLCBMTCArZ2ApLiBPdGhlcnMgYXJlIG1vcmUgZGlzc2ltaWxhciB0byB0aGUgcmVzdCwgZm9yIGV4YW1wbGUgYExDLCBJTGAgYW5kIGBMQywgTEwsICtGTGAuIFRoZXkgYXJlIG1vcmUgYWxpa2UgZWFjaCBvdGhlciwgYWx0aG91Z2ggYExDLCBMTCwgK0ZMYCBzaG91bGQgYmUgbW9yZSBjb21wYXJhYmxlIHRvIGBMQywgTExgLCBoaW50aW5nIGF0IGV4cGVyaW1lbnRhbCBiaWFzLiBJbiB0aGlzIGNhc2UgYm90aCBvZiB0aGVzZSBjb25kaXRpb25zIChhbmQgYExDLCBMTCwgK0dgKSB3ZXJlIHByZS1jdWx0aXZhdGVkIGluIGxvdyBsaWdodCBpbnN0ZWFkIG9mIGhpZ2ggbGlnaHQsIGFzIG9wcG9zZWQgdG8gdGhlIHJlc3Qgb2YgdGhlIHNhbXBsZXMuCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9CmRmX21haW4gJT4lIGZpbHRlcih0aW1lID09IDAsIHNnUk5BX2luZGV4ID09IDEpICU+JQogIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCBmaXRuZXNzKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGxvY3VzKSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGNvbmRpdGlvbiwgdmFsdWVzX2Zyb20gPSBmaXRuZXNzKSAlPiUKICBzZWxlY3QoLWxvY3VzKSAlPiUKICBjdXN0b21fc3Bsb20ocGNoID0gMTksIGNleCA9IDAuMywgY29sID0gZ3JleSgwLjQsIDAuNCksIHBzY2FsZXMgPSAwKQpgYGAKCgpBbm90aGVyIHdheSB0byBsb29rIGF0IHRoZSByZXN1bHQgb2YgdGhlIG5vcm1hbGl6YXRpb24gaXMgdG8gY29tcGFyZSB0aGUgZ2xvYmFsIGRpc3RyaWJ1dGlvbiBvZiBsb2cyIEZDIHZhbHVlcywgYXMgYSBkZW5zaXR5IHBsb3QuCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDYsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeShnZ3JpZGdlcykKZGZfbWFpbiAlPiUgZmlsdGVyKHRpbWUgPT0gMTApICU+JQogIHNlbGVjdChzZ1JOQSwgY29uZGl0aW9uLCBsb2cyRm9sZENoYW5nZSkgJT4lCiAgZGlzdGluY3QgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbG9nMkZvbGRDaGFuZ2UsIHkgPSBjb25kaXRpb24sIGdyb3VwID0gY29uZGl0aW9uKSkgKyAKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGZpbGwgPSAiIzAwQUZCQjk5IiwgY29sID0gZ3JleSgwLjQpKSArCiAgbGltcyh4ID0gYygtMiwgMS41KSkgKwogIGN1c3RvbV90aGVtZSgpCmBgYAoKIyBHZW5lIGZpdG5lc3MKClNnUk5BIGZpdG5lc3Mgc2NvcmUgd2FzIGNhbGN1bGF0ZWQgYXMgdGhlIEFVQyBvZiBsb2cyRkMgcmVhZCBudW1iZXIgb3ZlciBnZW5lcmF0aW9uIHRpbWUsIG5vcm1hbGl6ZWQgYnkgZ2VuZXJhdGlvbiB0aW1lLiBHZW5lIGZpdG5lc3Mgc2NvcmUgd2FzIGNhbGN1bGF0ZWQgYXMgdGhlIHdlaWdodGVkIG1lYW4gb2YgaW5kaXZpZHVhbCBzZ1JOQSBmaXRuZXNzIHNjb3Jlcy4gVGhlIHdlaWdodHMgYXJlIG1hZGUgdXAgb2YgdHdvIGRpZmZlcmVudCBjb21wb25lbnRzLAoKICAxLiBndWlkZSBSTkEgY29ycmVsYXRpb24KICAyLiBndWlkZSBSTkEgZWZmaWNpZW5jeQoKCiMjIEd1aWRlIFJOQSBjb3JyZWxhdGlvbgoKQSBjb3JyZWxhdGlvbiBzY29yZSB3YXMgY2FsY3VsYXRlZCBieSBjb21wdXRpbmcgdGhlIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IG9mIGFsbCBzZ1JOQXMgdG8gZWFjaCBvdGhlci4gVGhpcyBzY29yZSBpcyByb2J1c3RseSBzdW1tYXJpemVkIGJ5IHRha2luZyB0aGUgbWVkaWFuLCBhbmQgcmVzY2FsaW5nIGl0IGZyb20gdGhlIHJlc3BlY3RpdmUgbWluaW1hIGFuZCBtYXhpbWEgWy0xLCAxXSB0byBbMCwgMV0uIFRoaXMgc2NvcmUgc2VydmVkIGFzIGEgd2VpZ2h0IGNvbXBvbmVudCBmb3IgZWFjaCBzZ1JOQSB0byBjYWxjdWxhdGUgdGhlIChnbG9iYWwpIHdlaWdodGVkIG1lYW4gb2YgbG9nMiBGQyBhbmQgZml0bmVzcyBvdmVyIGFsbCBzZ1JOQXMuIFRoZSBzY29yZSBoYXMgdGhlIGNoYXJhY3RlcmlzdGljIHRoYXQgaXQgZ2l2ZXMgYSB3ZWlnaHQgb2YgMSBmb3IgYW4gc2dSTkEgcGVyZmVjdGx5IGNvcnJlbGF0ZWQgd2l0aCBhbGwgb3RoZXIgc2dSTkFzIG9mIHRoZSBzYW1lIGdlbmUsIGFuZCBhIHdlaWdodCBvZiAwIGZvciBzZ1JOQXMgcGVyZmVjdGx5IGFudGktY29ycmVsYXRlZCB0byB0aGUgb3RoZXIgc2dSTkFzLgoKRm9yIGEgbWF0cml4IG9mICR4ID0gMSAuLiBtJCBzZ1JOQXMgYW5kICR5ID0gMSAuLiBuJCBvYnNlcnZhdGlvbnMgKG1lYXN1cmVtZW50cyksIHRoZSBjb3JyZWxhdGlvbiAkUiQgb2Ygb25lIHNnUk5BIHRvIGFub3RoZXIgaXMgY2FsY3VsYXRlZCB1c2luZyBQZWFyc29uJ3MgbWV0aG9kOgoKJFJfeD1jb3IoW2xvZ18yRkNfe3gxLHkxfSAuLi4gbG9nXzJGQ197eDEseW59XSwgW2xvZ18yRkNfe3gyLHkxfSAuLi4gbG9nXzJGQ197eDIseW59XSkkCgpUaGUgY29ycmVsYXRpb24gd2VpZ2h0IG9mIG9uZSBzZ1JOQSBpcyB0aGVuIGNhbGN1bGF0ZWQgYXMgbWVkaWFuIG9mIGFsbCAkUiQgcmVzY2FsZWQgYmV0d2VlbiAwIGFuZCAxLgoKJHdfeCA9IFxmcmFjezEgKyBtZWRpYW4oUl8xLCBSXzIsIC4uLiwgUl9tKX17Mn0kCgpUaGUgZm9sbG93aW5nIGV4YW1wbGUgc2hvd3MgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBmb3IgdGhlIDUgYHJwczEwYCBzZ1JOQXMsIGFuZCB0aGVpciB3ZWlnaHRzLiBUaGUgc2VsZiBjb3JyZWxhdGlvbiBvZiBlYWNoIHNnUk5BIChSID0gMSkgaXMgcmVtb3ZlZCBwcmlvciB0byB3ZWlnaHQgZGV0ZXJtaW5hdGlvbi4KCmBgYHtyLCBmaWcud2lkdGggPSA0LCBmaWcuaGVpZ2h0ID0gNH0KY29yX21hdHJpeCA8LSBkZl9tYWluICU+JSBmaWx0ZXIoc2dSTkFfdGFyZ2V0ID09ICJycHMxMCIpICU+JSB1bmdyb3VwICU+JQogIHNlbGVjdChzZ1JOQV9pbmRleCwgbG9nMkZvbGRDaGFuZ2UsIGNvbmRpdGlvbiwgdGltZSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGMoImNvbmRpdGlvbiIsICJ0aW1lIiksIHZhbHVlc19mcm9tID0gbG9nMkZvbGRDaGFuZ2UpICU+JQogIGFycmFuZ2Uoc2dSTkFfaW5kZXgpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoInNnUk5BX2luZGV4IikgJT4lCiAgYXMubWF0cml4ICU+JSB0ICU+JSBjb3IobWV0aG9kID0gInBlYXJzb24iKQoKd2VpZ2h0cyA8LSBjb3JfbWF0cml4ICU+JSByZXBsYWNlKC4sIC4gPT0gMSwgTkEpICU+JQogIGFwcGx5KDIsIGZ1bmN0aW9uKHgpIG1lZGlhbih4LCBuYS5ybSA9IFRSVUUpKSAlPiUKICByZXNjYWxlKGZyb20gPSBjKC0xLCAxKSwgdG8gPSBjKDAsIDEpKQoKIyBwbG90IGhlYXRtYXAKbGF0dGljZTo6bGV2ZWxwbG90KGNvcl9tYXRyaXggJT4lIHJlcGxhY2UoLiwgLiA9PSAxLCBOQSksCiAgY29sLnJlZ2lvbnMgPSBjdXN0b21fcmFuZ2UoMjApKQoKIyBwcmludCB3ZWlnaHRzCndlaWdodHMKYGBgCgoKIyMgR3VpZGUgUk5BIGVmZmljaWVuY3kKClRoZSBjb3JyZWxhdGlvbiBvZiBlYWNoIHNnUk5BIHdpdGggZWFjaCBvdGhlciBpcyBhICJnbG9iYWwiIHBhcmFtZXRlciBhcyBpdCBpcyBpZGVudGljYWwgb3ZlciBhbGwgY29uZGl0aW9ucy4gQSBzZWNvbmQgZ2xvYmFsIHBhcmFtZXRlciwgKipzZ1JOQSBlZmZpY2llbmN5KiosIHdhcyBvYnRhaW5lZCB1c2luZyBhIHNpbWlsYXIgYXBwcm9hY2guIFdlIGV4cGVjdCB0aGF0IGZpdG5lc3Mgb2YgYWxsIHNnUk5BcyBmb3Igb25lIGdlbmUgaXMgbm90IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGJlY2F1c2Ugc2dSTkFzIGFyZSBub3QgaWRlYWwgcmVwbGljYXRlIG1lYXN1cmVtZW50cy4gVGhleSBhcmUgYmlhc2VkIGJ5IHBvc2l0aW9uIGVmZmVjdHMgYW5kIG9mZi10YXJnZXQgYmluZGluZywgc2VlIFtXYW5nIGV0IGFsLiwgTmF0dXJlIENvbW1zLCAyMDE4XShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL3M0MTQ2Ny0wMTgtMDQ4OTkteCkgZm9yIGEgdmVyeSBpbnNpZ2h0ZnVsIGFuZCBjb21wcmVoZW5zaXZlIGFuYWx5c2lzIG9mIHRoZSBudW1iZXIgYW5kIHBvc2l0aW9uIG9mIHNnUk5BcyByZXF1aXJlZCB0byBlc3RpbWF0ZSBnZW5lIGZpdG5lc3MuCgpIZXJlLCBzZ1JOQSBlZmZpY2llbmN5ICRFJCB3YXMgY2FsY3VsYXRlZCBhcyB0aGUgbWVkaWFuIGFic29sdXRlIGZpdG5lc3MgKEFVQyBvZiBsb2cyRkMgb3ZlciB0aW1lKSBvZiBhbiBzZ1JOQSAkeCA9IDEgLi4gbSQgb3ZlciBhbGwgb2JzZXJ2YXRpb25zIFtjb25kaXRpb25zXSAkeSA9IDEgLi4gbiQuCgokRV94PW1lZGlhbihhYnMoZml0bmVzc197eDEsIHkxfSwgZml0bmVzc197eDEsIHkyfSwgLi4uLCBmaXRuZXNzX3t4MSwgeW59KSkkCgpUbyBub3JtYWxpemUgYmV0d2VlbiBhbGwgc2dSTkFzLCAkRSQgaXMgcmVzY2FsZWQgdG8gYSByYW5nZSBiZXR3ZWVuIDAgYW5kIDEuCgokRV94PVxmcmFje0VfeH17bWF4KEVfMSwgRV8yLCAuLi4sIEVfbSl9JAoKClRoaXMgaXMgdGhlIHJlc3VsdGluZyBzZ1JOQSBlZmZpY2llbmN5IGZvciB0aGUgZXhhbXBsZSBnZW5lIGFib3ZlLCBgcnBzMTBgLgoKYGBge3J9CmRmX21haW4gJT4lIGZpbHRlcihzZ1JOQV90YXJnZXQgPT0gInJwczEwIikgJT4lIHVuZ3JvdXAgJT4lCiAgc2VsZWN0KHNnUk5BX2luZGV4LCBzZ1JOQV9lZmZpY2llbmN5KSAlPiUgZGlzdGluY3QgJT4lIAogIGFycmFuZ2Uoc2dSTkFfaW5kZXgpICU+JSBkZWZyYW1lCmBgYAoKCiMjIFBvc2l0aW9uIGJpYXMgb2Ygc2dSTkEgcmVwcmVzc2lvbgoKUGxvdCB0aGUgKip3ZWlnaHQgb2YgZWFjaCBzZ1JOQSoqIHRvIHNlZSBpZiB0aGVyZSBpcyBhIGRlcGVuZGVuY3kgYmV0d2VlbiBjb3JyZWxhdGlvbiBhbmQgc2dSTkEgcG9zaXRpb24uIFRoZXJlIGlzIG5vIHNpZ25pZmljYW50IHRyZW5kLgoKV2UgY2FuIGFsc28gcXVhbnRpZnkgaG93IG1hbnkgZ2VuZXMgaGF2ZSBzdHJvbmdseSBjb3JyZWxhdGVkIHNnUk5BcyBhbmQgaG93IG1hbnkgaGF2ZSBvdXRsaWVycy4gSW4gb3JkZXIgdG8gZG8gdGhpcywgdGhlIG1lZGlhbiB3ZWlnaHQgb2YgdGhlICh1cCB0bykgNSBzZ1JOQXMgcGVyIGdlbmUgaXMgcGxvdHRlZC4gR2VuZXJhbGx5LCB0aGUgbWVkaWFuIHdlaWdodCByYW5nZXMgYmV0d2VlbiAwLjUgYW5kIDEuMCwgc2hvd2luZyBvbiBhdmVyYWdlIGdvb2QgY29ycmVsYXRpb24uCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodCA9IDN9CnBsb3Rfc2dSTkFfY29ycmVsYXRpb24gPC0gZGZfbWFpbiAlPiUKICBzZWxlY3Qoc2dSTkFfdGFyZ2V0LCBzZ1JOQV9pbmRleCwgc2dSTkFfY29ycmVsYXRpb24sIHNnUk5BX3R5cGUpICU+JQogIGZpbHRlcihzZ1JOQV9pbmRleCA8PSA1LCBzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgZGlzdGluY3QgJT4lCiAgIyBwbG90CiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHNnUk5BX2luZGV4KSwgeSA9IHNnUk5BX2NvcnJlbGF0aW9uKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gIiIpICsKICBsYWJzKHggPSAic2dSTkEgcG9zaXRpb24gKHJlbGF0aXZlKSIsIHkgPSAiY29ycmVsYXRpb24iKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgYyh5ID0gbWVkaWFuKHgpKzAuMDcsIAogICAgbGFiZWwgPSByb3VuZChtZWRpYW4oeCksIDIpKSwgZ2VvbSA9ICJ0ZXh0Iiwgc2l6ZSA9IDMpICsKICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBjKHkgPSAxLjEsIAogICAgbGFiZWwgPSBsZW5ndGgoeCkpLCBnZW9tID0gInRleHQiLCBjb2xvciA9IGdyZXkoMC41KSwgc2l6ZSA9IDMpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTAuMTUsIDEuMTUpKSArCiAgY3VzdG9tX3RoZW1lKCkKCnBsb3Rfc2dSTkFfY29ycmVsYXRpb25faGlzdCA8LSBkZl9tYWluICU+JQogIHNlbGVjdChzZ1JOQV90YXJnZXQsIHNnUk5BX2luZGV4LCBzZ1JOQV9jb3JyZWxhdGlvbiwgc2dSTkFfdHlwZSkgJT4lCiAgZmlsdGVyKHNnUk5BX2luZGV4IDw9IDUsIHNnUk5BX3R5cGUgPT0gImdlbmUiKSAlPiUKICBkaXN0aW5jdCAlPiUgZ3JvdXBfYnkoc2dSTkFfdGFyZ2V0KSAlPiUKICBzdW1tYXJpemUoCiAgICBtZWRpYW5fc2dSTkFfY29ycmVsYXRpb24gPSBtZWRpYW4oc2dSTkFfY29ycmVsYXRpb24pLAogICAgbWluX3NnUk5BX2NvcnJlbGF0aW9uID0gbWluKHNnUk5BX2NvcnJlbGF0aW9uKQogICkgJT4lCiAgIyBwbG90CiAgZ2dwbG90KGFlcyh4ID0gbWVkaWFuX3NnUk5BX2NvcnJlbGF0aW9uKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA0MCwgZmlsbCA9IGN1c3RvbV9jb2xvcnNbMV0sIGFscGhhID0gMC43KSArCiAgY3VzdG9tX3RoZW1lKCkKCmdnYXJyYW5nZShwbG90X3NnUk5BX2NvcnJlbGF0aW9uLCBwbG90X3NnUk5BX2NvcnJlbGF0aW9uX2hpc3QsIG5jb2wgPSAyKQpgYGAKClNlY29uZCwgdGhlIGJpbmRpbmcgcG9zaXRpb24gb2YgdGhlIHNnUk5BcyBjb3VsZCBiZSBjb3JyZWxhdGVkIHRvIHRoZSBzdHJlbmd0aCBvZiByZXByZXNzaW9uLiBJbiBvdGhlciB3b3JkcyBzZ1JOQXMgYmluZGluZyBjbG9zZXIgdG8gdGhlIHByb21vdGVyIGNvdWxkIGhhdmUgc3Ryb25nZXIgYWJpbGl0eSB0byByZXByZXNzIGEgZ2VuZSwgc2VlIEZpZ3VyZSAxIEIgaW4gW1dhbmcgZXQgYWwuLCBOYXR1cmUgQ29tbXMsIDIwMThdKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNDY3LTAxOC0wNDg5OS14KS4gV2UgcGxvdCAqKnNnUk5BIGVmZmljaWVuY3kqKiBmb3IgZ2VuZXMgb25seSwgYmVjYXVzZSB0aGUgYWJzb2x1dGUgbWFqb3JpdHkgb2YgdGhvc2UgaGFzIDUgc2dSTkFzLgoKYGBge3IsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSAzfQpwbG90X3NnUk5BX2VmZmljaWVuY3kgPC0gZGZfbWFpbiAlPiUKICBmaWx0ZXIoc2dSTkFfaW5kZXggPD0gNSwgc2dSTkFfdHlwZSA9PSAiZ2VuZSIpICU+JQogIHNlbGVjdChzZ1JOQV90YXJnZXQsIHNnUk5BX2luZGV4LCBzZ1JOQV9lZmZpY2llbmN5KSAlPiUgZGlzdGluY3QgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHNnUk5BX2luZGV4KSwgeSA9IHNnUk5BX2VmZmljaWVuY3kpKSArCiAgZ2VvbV9ib3hwbG90KG5vdGNoID0gRkFMU0UsIG91dGxpZXIuc2hhcGUgPSAiLiIpICsKICBsYWJzKHggPSAic2dSTkEgcG9zaXRpb24gKHJlbGF0aXZlKSIsIHkgPSAicmVwcmVzc2lvbiBlZmZpY2llbmN5IikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtMC4xNSwgMS4xNSkpICsKICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBjKHkgPSBtZWRpYW4oeCkrMC4wNywgCiAgICBsYWJlbCA9IHJvdW5kKG1lZGlhbih4KSwgMikpLCBnZW9tID0gInRleHQiLCBzaXplID0gMykgKwogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGMoeSA9IDEuMSwgCiAgICBsYWJlbCA9IGxlbmd0aCh4KSksIGdlb20gPSAidGV4dCIsIGNvbG9yID0gZ3JleSgwLjUpLCBzaXplID0gMykgKwogIGN1c3RvbV90aGVtZSgpCgoKcGxvdF9zZ1JOQV9lZmZpY2llbmN5X2hpc3QgPC0gZGZfbWFpbiAlPiUKICBmaWx0ZXIoc2dSTkFfaW5kZXggPD0gNSwgc2dSTkFfdHlwZSA9PSAiZ2VuZSIpICU+JQogIHNlbGVjdChzZ1JOQV90YXJnZXQsIHNnUk5BX3Bvc2l0aW9uLCBzZ1JOQV9lZmZpY2llbmN5KSAlPiUgZGlzdGluY3QgJT4lCiAgZ3JvdXBfYnkoc2dSTkFfcG9zaXRpb24pICU+JQogIHN1bW1hcml6ZShzZ1JOQV9lZmZpY2llbmN5ID0gbWVkaWFuKHNnUk5BX2VmZmljaWVuY3kpLCBuX3BvcyA9IG4oKSkgJT4lCiAgZmlsdGVyKG5fcG9zID49IDEwKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBzZ1JOQV9wb3NpdGlvbiwgeSA9IHNnUk5BX2VmZmljaWVuY3kpKSArCiAgbGFicyh4ID0gInNnUk5BIHBvc2l0aW9uIChudCkiLCB5ID0gInJlcHJlc3Npb24gZWZmaWNpZW5jeSIpICsKICBnZW9tX3BvaW50KGNvbCA9IGFscGhhKGN1c3RvbV9jb2xvcnNbNV0sIDAuNSkpICsKICBnZW9tX3Ntb290aCgpICsKICBjdXN0b21fdGhlbWUoKQoKZ2dhcnJhbmdlKHBsb3Rfc2dSTkFfZWZmaWNpZW5jeSwgcGxvdF9zZ1JOQV9lZmZpY2llbmN5X2hpc3QsIG5jb2wgPSAyKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDMuNX0KcGxvdF9zZWxlY3RlZF9zZ1JOQXMgPC0gZGZfbWFpbiAlPiUKICBmaWx0ZXIoCiAgICBncmVwbCgiY3RybFsxLTVdJHxycHMxMCQiLCBzZ1JOQV90YXJnZXQpLCAKICAgIGNvbmRpdGlvbiAlaW4lIGMoIkhDLCBITCIsICJIQywgTEwiLCAiTEMsIElMIiwgIkxDLCBMTCIpKSAlPiUKICBtdXRhdGUoCiAgICBzZ1JOQV9pbmRleDIgPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KHNnUk5BX3RhcmdldCwgIlsxLTldJCIpKSwKICAgIHNnUk5BX2luZGV4ID0gY2FzZV93aGVuKHNnUk5BX3Bvc2l0aW9uID09IDAgfiBzZ1JOQV9pbmRleDIsIFRSVUUgfiBzZ1JOQV9pbmRleCksCiAgICBzZ1JOQV90YXJnZXQgPSBzdHJfZXh0cmFjdChzZ1JOQV90YXJnZXQsICJbYS16QS1aXSoiKQogICkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGxvZzJGb2xkQ2hhbmdlLCBjb2xvciA9IGZhY3RvcihzZ1JOQV9pbmRleCkpKSArCiAgZ2VvbV9saW5lKHNpemUgPSAxKSArIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsKICBsYWJzKHggPSAidGltZSBbaF0iLCB5ID0gZXhwcmVzc2lvbigibG9nIlsyXSoiIEZDIikpKwogIGZhY2V0X2dyaWQoc2dSTkFfdGFyZ2V0IH4gY29uZGl0aW9uKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IDApICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTQuNSwgMi41KSkgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDAsMiw0LDYsOCwxMCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX3JhbmdlKDUpKQoKcGxvdF9zZWxlY3RlZF9zZ1JOQXMKYGBgCgpFeHBvcnQgKipzdXBwbGVtZW50YWwgZmlndXJlIHdpdGggYWxsIHJpYm9zb21hbCBnZW5lcyoqIChycHMqTk4qL3JwbCpOTiopLgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSAxMH0KcGxvdF9zZ1JOQXNfcmlib3NvbWUgPC0gZGZfbWFpbiAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChzZ1JOQV90YXJnZXQsICJycFtzbF1bMC05XSokIikpICU+JQogIGZpbHRlcihjb25kaXRpb24gPT0gIkxDLCBMTCIpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBsb2cyRm9sZENoYW5nZSwgY29sb3IgPSBmYWN0b3Ioc2dSTkFfaW5kZXgpKSkgKwogIGdlb21fbGluZShzaXplID0gMSkgKyBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgZmFjZXRfd3JhcCh+IHNnUk5BX3RhcmdldCwgbmNvbCA9IDcpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX3JhbmdlKDUpKQoKcHJpbnQocGxvdF9zZ1JOQXNfcmlib3NvbWUpCmBgYAoKV2UgZ2VuZXJhdGUgYSByZWR1Y2VkIHRhYmxlIGZvY3VzaW5nIG9uIGdlbmUgZml0bmVzcyBpbnN0ZWFzIG9mIHNnUk5BIGZpdG5lc3MuClRoZSBkYXRhIHNldCBhbHJlYWR5IGNvbnRhaW5zIGd1aWRlIFJOQSBsZXZlbCBwLXZhbHVlcyBmcm9tIERFU2VxMiAoYHB2YWxgLCBgcGFkamApLCBhbmQgZ2VuZS1iYXNlZCBwLXZhbHVlcyBmcm9tIGZyb20gV2lsY294b24gUmFuayBTdW0gdGVzdCAoYHBfZml0bmVzc2AsIGBwX2ZpdG5lc3NfYWRqYCkuIFNpbmNlIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSB3YXMgdGVzdGVkIGZvciBtYW55IGdlbmVzIGluIHBhcmFsbGVsLCB0aGUgc2Vjb25kIGNvbHVtbnMgb2YgcC12YWx1ZXMgcmVwcmVzZW50cyBtdWx0aXBsZS1oeXBvdGhlc2lzIGNvcnJlY3RlZCBwLXZhbHVlcy4gVGhlIEJlbmphbWluaS1Ib2NoYmVyZyBtZXRob2Qgd2FzIHVzZWQgZm9yIHRoaXMgcHVycG9zZS4gVGhlIHBpcGVsaW5lIGFsc28gb3V0cHV0cyBhIHNjb3JlIHRoYXQgdGFrZXMgYm90aCBlZmZlY3Qgc2l6ZSBhbmQgcC12YWx1ZSBpbnRvIGFjY291bnQgKGBjb21iX3Njb3JlYCksIGFjY29yZGluZyB0byB0aGUgcHVibGljYXRpb24gZnJvbSBbV2FuZyBldCBhbC4sIE5hdCBDb21tLCAyMDE4XShodHRwOi8vZHguZG9pLm9yZy8xMC4xMDM4L3M0MTQ2Ny0wMTgtMDQ4OTkteCkuIFRoaXMgc2NvcmUgaXMgc2ltcGx5IHRoZSBhYnNvbHV0ZSBmaXRuZXNzIHNjb3JlIG11bHRpcGxpZWQgYnkgdGhlIG5lZ2F0aXZlIGxvZzEwIHAtdmFsdWUuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmRmX2dlbmUgPC0gZGZfbWFpbiAlPiUKICBzZWxlY3Qoc2dSTkFfdGFyZ2V0LCBzZ1JOQV90eXBlLCBsb2N1cywKICAgIGdlbmVfbmFtZSwgY29uZGl0aW9uLCAKICAgIGNhcmJvbiwgbGlnaHQsIHRyZWF0bWVudCwgdGltZSwKICAgIHdtZWFuX2xvZzJGb2xkQ2hhbmdlLCBzZF9sb2cyRm9sZENoYW5nZSwKICAgIHdtZWFuX2ZpdG5lc3MsIHNkX2ZpdG5lc3MsCiAgICBwX2ZpdG5lc3NfYWRqLCBjb21iX3Njb3JlCiAgKSAlPiUgZGlzdGluY3QKYGBgCgoKIyMgR2xvYmFsIGRpc3RyaWJ1dGlvbiBvZiBnZW5lIGZpdG5lc3MKCkdsb2JhbCBkaXN0cmlidXRpb24gb2Ygd2VpZ2h0ZWQgbWVhbiBmaXRuZXNzIGZvciBhbGwgZ2VuZXMuIEVmZmVjdCBvZiBuY1JOQSByZXByZXNzaW9uIHNlZW1zIHRvIGJlIG11Y2ggbG93ZXIgdGhhbiBlZmZlY3Qgb2YgZ2VuZSByZXByZXNzaW9uLgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1fQpkZl9oaXN0X3N0YXRzIDwtIGRmX2dlbmUgJT4lCiAgZmlsdGVyKHRpbWUgPT0gMCkgJT4lCiAgbXV0YXRlKGl2ID0gY3V0KHdtZWFuX2ZpdG5lc3MsIGJyZWFrcyA9IGMoLTQsLTIsMiw0KSkpICU+JQogIGdyb3VwX2J5KGNvbmRpdGlvbiwgaXYpICU+JQogIGNvdW50KCkgJT4lIAogIG11dGF0ZShwZXJjZW50ID0gMTAwKm4vbGVuZ3RoKHVuaXF1ZShkZl9nZW5lJHNnUk5BX3RhcmdldCkpKSAlPiUKICBzZXBhcmF0ZShpdiwgaW50byA9IGMoIngxIiwgIngyIiksIHNlcCA9ICIsIikgJT4lCiAgbXV0YXRlKHgxID0gYXMubnVtZXJpYyhzdHJfZXh0cmFjdCh4MSwgIlxcLT9bMC05XSIpKSArIDAuMikgJT4lCiAgbXV0YXRlKHgyID0gYXMubnVtZXJpYyhzdHJfZXh0cmFjdCh4MiwgIlxcLT9bMC05XSIpKSAtIDAuMikgJT4lCiAgZmlsdGVyKCFpcy5uYSh4MSkpCgpwbG90X2FsbF9maXRuZXNzX2hpc3QgPC0gZGZfZ2VuZSAlPiUKICBmaWx0ZXIodGltZSA9PSAwKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gd21lYW5fZml0bmVzcykpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0yLCAyKSwgY29sID0gZ3JleSgwLjUpLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwLCBhZXMoZmlsbCA9IHNnUk5BX3R5cGUpKSArCiAgZ2VvbV9icmFja2V0KGRhdGEgPSBkZl9oaXN0X3N0YXRzLAogICAgbWFwcGluZyA9IGFlcyh4bWluID0geDEsIHhtYXggPSB4MiwgbGFiZWwgPSBwYXN0ZTAocm91bmQocGVyY2VudCwgMSksICIlIikpLAogICAgeS5wb3NpdGlvbiA9IDgwMCwgY29sb3IgPSBncmV5KDAuNSksIAogICAgbGFiZWwuc2l6ZSA9IDMpICsKICBsYWJzKHggPSAiZml0bmVzcyIsIHkgPSIiKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC00LCA0KSwgeWxpbSA9IGMoMCwgMTAwMCkpICsKICBmYWNldF93cmFwKCB+IGNvbmRpdGlvbiwgbmNvbCA9IDYpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIGFzcGVjdCA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzW2MoMzo0KV0pCgpwcmludChwbG90X2FsbF9maXRuZXNzX2hpc3QpCmBgYAoKIyMgR2VuZSBmaXRuZXNzIHZzIHNpZ25pZmljYW5jZQoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1fQpwbG90X2FsbF9maXRuZXNzX3ZvbGMgPC0gZGZfZ2VuZSAlPiUKICBhcnJhbmdlKHNnUk5BX3R5cGUpICU+JQogIGdncGxvdChhZXMoeCA9IHdtZWFuX2ZpdG5lc3MsIHkgPSAtbG9nMTAocF9maXRuZXNzX2FkaiksIGNvbCA9IHNnUk5BX3R5cGUpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDEsIHNpemUgPSAwLjEpICsKICBnZW9tX2xpbmUoZGF0YSA9IGRhdGEuZnJhbWUoeCA9IGMoc2VxKC04LCAtMC41LCAwLjEpLCBzZXEoMC41LCA4LCAwLjEpKSwKICAgIHkgPSA0L2Moc2VxKDgsIDAuNSwgLTAuMSksIHNlcSgwLjUsIDgsIDAuMSkpKSwKICAgIGFlcyh4ID0geCwgeSA9IHksIHNoYXBlID0gTlVMTCksIGx0eSA9IDIsIGNvbG9yID0gZ3JleSgwLjUpLCBzaXplID0gMC42KSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC03LCA3KSwgeWxpbSA9IGMoMCwgMi41KSkgKwogIGN1c3RvbV90aGVtZShhc3BlY3QgPSAxLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjQsICJjbSIpKSArCiAgZmFjZXRfd3JhcCh+IGNvbmRpdGlvbiwgbmNvbCA9IDYpICsKICBsYWJzKHggPSAiZml0bmVzcyIsIHkgPSBleHByZXNzaW9uKCItbG9nIlsxMF0qIiBwLXZhbHVlIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9yc1szOjRdKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDEsIDE5KSkKCnByaW50KHBsb3RfYWxsX2ZpdG5lc3Nfdm9sYykKYGBgCgojIyBCZWhhdmlvciBvZiBjb250cm9sIHNnUk5BcwoKVGVuIHNnUk5BcyB3ZXJlIGluY2x1ZGVkIGluIHRoZSBsaWJyYXJ5IHRoYXQgaGF2ZSBubyBnZW5lLXNwZWNpZmljIHRhcmdldHMuIFRoZSBmb2xsb3dpbmcgcGxvdCBzaG93cyB0aGF0IHRoZXNlIG5lZ2F0aXZlIGNvbnRyb2xzIGRvIG5vdCBoYXZlIGFuIGVmZmVjdCBvbiBzdHJhaW4gZml0bmVzcywgZXhjZXB0IHByb2JhYmx5IDIgc2dSTkFzIGluIG9uZSBzcGVjaWZpYyBjb25kaXRpb24uCgpgYGB7ciwgZmlnLndpZHRoID0gNi41LCBmaWcuaGVpZ2h0ID0gNX0KcGxvdF9jb250cm9sc19zZ1JOQXMgPC0gZGZfbWFpbiAlPiUgZmlsdGVyKGdyZXBsKCJjdHJsIiwgc2dSTkFfdGFyZ2V0KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGxvZzJGb2xkQ2hhbmdlLCBjb2xvciA9IHNnUk5BX3RhcmdldCkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyB5bGltKC01LCA1KSArCiAgZmFjZXRfd3JhcCh+IGNvbmRpdGlvbiwgbmNvbCA9IDQpICsKICBjdXN0b21fdGhlbWUoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9yYW5nZSgxMCkpCgpwcmludChwbG90X2NvbnRyb2xzX3NnUk5BcykKYGBgCgpFeHBvcnQgZHJhZnQgKipGaWd1cmUgMSoqIGZvciBtYW51c2NyaXB0LgoKYGBge3IsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA4LjV9CnN2ZyhmaWxlbmFtZSA9ICIuLi9maWd1cmVzL2ZpZ3VyZTEuc3ZnIiwgd2lkdGggPSA3LCBoZWlnaHQgPSA4LjUpCmdnYXJyYW5nZShuY29sID0gMiwgbnJvdyA9IDMsCiAgaGVpZ2h0cyA9IGMoYygwLjMsIDAuMywgMC40KSksIHdpZHRocyA9IGMoMC42LCAwLjQpLAogIGxhYmVscyA9IGMoIkEiLCAiQyIsICJCIiwgIkQiLCAiRSIpLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X3NnUk5Bc19wZXJfZ2VuZSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLDEyLDEyLDEyKSwgInBvaW50cyIpKSwKICBwbG90X3NnUk5BX2VmZmljaWVuY3kgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygyNiwxMiwxMiwxMiksICJwb2ludHMiKSksCiAgcGxvdF9zZWxlY3RlZF9zZ1JOQXMgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwtNCwxMiwxNCksICJwb2ludHMiKSksCiAgcGxvdF9zZ1JOQV9jb3JyZWxhdGlvbiArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDI2LDEyLDEyLDEyKSwgInBvaW50cyIpKSwKICBwbG90X2FsbF9maXRuZXNzX2hpc3QgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygtMzYsLTE5MCwtNDgsIDEyKSwgInBvaW50cyIpKQopCmRldi5vZmYoKQpgYGAKCgojIEdlbmUgZW5yaWNobWVudAoKVG8gcGxvdCBnZW5lIGZpdG5lc3MgZm9yIHRoZSBlbnp5bWVzIG9mIGNlbnRyYWwgY2FyYm9uIG1ldGFib2xpc20sIHdlIG5lZWQgYSBjb21wbGV0ZSBsaXN0IG9mIGVuenltZXMgYW5kIHRoZSBnZW5lcyB0aGF0IHRoZXkgYXJlIG1hcHBlZCB0by4gVG8gbGlzdCB0aGUgZGlmZmVyZW50ICoqS0VHRyBkYXRhYmFzZXMqKiB0aGF0IGNhbiBiZSBxdWVyaWVkLCB1c2UgYGxpc3REYXRhYmFzZXMoKWAuIEdlbmUtcGF0aHdheSBtYXBwaW5ncyBhcmUgb2J0YWluZWQgYW5kIG1lcmdlZCB3aXRoIHBhdGh3YXkgbmFtZXMgYW5kIGdlbmUvZW56eW1lIG5hbWVzLgoKCmBgYHtyfQojIGdldCBtYXBwaW5nIG9mIHBhdGh3YXlzIGZvciBlYWNoIGdlbmUKZGZfa2VnZyA8LSBrZWdnTGluaygicGF0aHdheSIsICJzeW4iKSAlPiUKICBlbmZyYW1lKG5hbWUgPSAibG9jdXMiLCB2YWx1ZSA9ICJrZWdnX3BhdGh3YXlfaWQiKSAlPiUKICAKICAjIGdldCBsaXN0IG9mIHBhdGh3YXlzIHdpdGggbmFtZS9JRCBwYWlycwogIGxlZnRfam9pbihieSA9ICJrZWdnX3BhdGh3YXlfaWQiLAogICAga2VnZ0xpc3QoInBhdGh3YXkiLCAic3luIikgJT4lCiAgICBlbmZyYW1lKG5hbWUgPSAia2VnZ19wYXRod2F5X2lkIiwgdmFsdWUgPSAia2VnZ19wYXRod2F5IikKICApICU+JQogIAogICMgZ2V0IGxpc3Qgb2YgZ2VuZS9lbnp5bWUgbmFtZXMKICBsZWZ0X2pvaW4oYnkgPSAibG9jdXMiLAogICAga2VnZ0xpc3QoInN5biIpICU+JQogICAgZW5mcmFtZShuYW1lID0gImxvY3VzIiwgdmFsdWUgPSAia2VnZ19nZW5lIikgJT4lCiAgICBtdXRhdGUoa2VnZ19nZW5lX3Nob3J0ID0gc3RyX2V4dHJhY3Qoa2VnZ19nZW5lLCAiXlthLXpBLVowLTldKjsiKSAlPiUgCiAgICAgIHN0cl9yZW1vdmUoIjsiKSkKICApICU+JQogIAogICMgdHJpbSB1c2VsZXNzIHByZWZpeGVzCiAgbXV0YXRlKAogICAgbG9jdXMgPSBzdHJfcmVtb3ZlKGxvY3VzLCAic3luOiIpLAogICAga2VnZ19wYXRod2F5X2lkID0gc3RyX3JlbW92ZShrZWdnX3BhdGh3YXlfaWQsICJwYXRoOiIpLAogICAga2VnZ19wYXRod2F5ID0gc3RyX3JlbW92ZShrZWdnX3BhdGh3YXksICIgLSBTeW5lY2hvY3lzdGlzIHNwLiBQQ0MgNjgwMyIpCiAgKQoKaGVhZChkZl9rZWdnKQpgYGAKCiMjIEZpdG5lc3MgcGVyIHBhdGh3YXkKClNvbWV0aW1lcyBldmVuIHNtYWxsIGVmZmVjdHMgaW4gZml0bmVzcyBjYW4gYmUgcmVsZXZhbnQgaWYgc2V2ZXJhbCBnZW5lcyBvZiB0aGUgc2FtZSBwYXRod2F5IChvciBpc28tZW56eW1lcykgYXJlIGFmZmVjdGVkLiBBIHNpbXBsZSBmaXRuZXNzIHRocmVzaG9sZCB3aWxsIG5vdCByZXZlYWwgdGhvc2UgY2hhbmdlcy4gSW4gc3VjaCBjYXNlcyBhIG1vcmUgbnVhbmNlZCBhcHByb2FjaCBjYW4gYmUgdGFrZW4sIGEgZ2VuZSBzZXQgZW5yaWNobWVudCBhbmFseXNpcyAoR1NFQSkuIFNldmVyYWwgcGFja2FnZXMgZXhpc3QgdG8gdGVzdCBpZiBmdW5jdGlvbmFsbHkgcmVsYXRlZCBnZW5lcyBhcmUgZW5yaWNoZWQsIGRlcGxldGVkLCBvciBib3RoIGF0IHRoZSBzYW1lIHRpbWUgLyB0aGUgc2FtZSBjb25kaXRpb25zLgoKQmVmb3JlIHdlIHRlc3QgZm9yIGVucmljaG1lbnQgb2YgYXNzb2NpYXRlZCBwYXRod2F5cy9HTyB0ZXJtcywgd2UgY2FuIGhhdmUgYSBsb29rIGF0IHRoZSBnZW5lcmFsIGRlcGxldGlvbi9lbnJpY2htZW50IHBlciBLRUdHIHBhdGh3YXkuIFRoZSBmaXRuZXNzIGRpc3RyaWJ1dGlvbiBwZXIgcGF0aHdheSBjYW4gYmUgdmlzdWFsaXplZCB1c2luZyBhIHZpb2xpbi0gb3Igc2NhdHRlciBwbG90LgoKYGBge3IsIGZpZy53aWR0aCA9IDcuMCwgZmlnLmhlaWdodCA9IDIuOH0KcGxvdF9tZWRpYW5fZml0bmVzc19rZWdnIDwtIGRmX2dlbmUgJT4lIGZpbHRlcih0aW1lID09IDApICU+JQogIGlubmVyX2pvaW4oZGZfa2VnZywgYnkgPSAibG9jdXMiKSAlPiUKICBncm91cF9ieShrZWdnX3BhdGh3YXksIGNvbmRpdGlvbikgJT4lCiAgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIsCiAgICBmaXRuZXNzID0gbWVkaWFuKHdtZWFuX2ZpdG5lc3MpLAogICAgbl9nZW5lcyA9IG4oKQogICkgJT4lIGZpbHRlcihuX2dlbmVzID49IDIwKSAlPiUKICBtdXRhdGUoCiAgICBrZWdnX3BhdGh3YXkgPSBzdHJfcmVtb3ZlX2FsbChrZWdnX3BhdGh3YXksICIgW01tXWV0YWJvbGlzbXxCaW9zeW50aGVzaXMgb2YgIiksCiAgICBrZWdnX3BhdGh3YXkgPSBzYXBwbHkoa2VnZ19wYXRod2F5LCBmdW5jdGlvbih4KSB7CiAgICAgIGlmIChzdHJfbGVuZ3RoKHgpIDw9IDE1KSB0b2xvd2VyKHgpCiAgICAgIGVsc2UgcGFzdGUwKHRvbG93ZXIoc3Vic3RyKHgsIDEsIDE1KSksICIuLiIpCiAgICB9KSwKICAgIGtlZ2dfcGF0aHdheSA9IGZjdF9yZW9yZGVyKGtlZ2dfcGF0aHdheSwgZml0bmVzcywgLmRlc2MgPSBUUlVFKQogICkgJT4lCiAgCiAgZ2dwbG90KGFlcyh4ID0ga2VnZ19wYXRod2F5LCB5ID0gZml0bmVzcykpICsKICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5VTEwsIGNvbG9yID0gZ3JleSgwLjUpLCBmaWxsID0gZ3JleSgwLjkpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjb25kaXRpb24pKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMiwgY29sb3IgPSBncmV5KDAuNSkpICsKICBsYWJzKHggPSAiIiwgeSA9ICJtZWRpYW4gZml0bmVzcyIpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjE1LCAwLjE1KSwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjEsICJjbSIpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzNSwgdmp1c3QgPSAxLjA1LCBoanVzdCA9IDEpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzWzE6NV0pKDExKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvclJhbXBQYWxldHRlKGN1c3RvbV9jb2xvcnNbMTo1XSkoMTEpKQoKcHJpbnQocGxvdF9tZWRpYW5fZml0bmVzc19rZWdnKQpgYGAKYGBge3J9CnN2ZyhmaWxlbmFtZSA9ICIuLi9maWd1cmVzL3Bsb3RfbWVkaWFuX2ZpdG5lc3Nfa2VnZy5zdmciLCB3aWR0aCA9IDcsIGhlaWdodCA9IDIuOCkKZ2dhcnJhbmdlKG5jb2wgPSAxLCBucm93ID0gMSwgbGFiZWxzID0gIkYiLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X21lZGlhbl9maXRuZXNzX2tlZ2cgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwxMiwxMiwyNSksICJwb2ludHMiKSkKKQpkZXYub2ZmKCkKYGBgCgoKIyMgR2VuZSBlbnJpY2htZW50IGFuYWx5c2lzIChLRUdHKQoKV2UgdXNlIHRoZSBmdW5jdGlvbnMgYGtlZ2dhYCBmb3IgS0VHRyBlbnJpY2htZW50IGFuYWx5c2lzIGFuZCBgZ29hbmFgIGZvciBHTyB0ZXJtIGVucmljaG1lbnQgZnJvbSB0aGUgYGxpbW1hYCBwYWNrYWdlLiBCb3RoIGZ1bmN0aW9ucyB0ZXN0IGZvciBvdmVyIG9yIHVuZGVyLXJlcHJlc2VudGF0aW9uIG9mIGdlbmVzIGFzc29jaWF0ZWQgd2l0aCBjZXJ0YWluIHBhdGh3YXlzIG9yIEdPIHRlcm1zLiBUaGUgZnVuY3Rpb25zIGRvbid0IHRha2UgdGhlIHN0cmVuZ3RoIG9mIGRpZmZlcmVudGlhbCBmaXRuZXNzIGludG8gYWNjb3VudCAoREY7IHRoZSBkZXBsZXRpb24vZW5yaWNobWVudCBvdmVyIHRpbWUpLgoKCmBgYHtyfQpkZl9rZWdnX2VucmljaG1lbnQgPC0gbGFwcGx5KHVuaXF1ZShkZl9nZW5lJGNvbmRpdGlvbiksIGZ1bmN0aW9uKGNvbmQpIHsKICBkZl9nZW5lICU+JSBmaWx0ZXIoCiAgc2dSTkFfdHlwZSA9PSAiZ2VuZSIsIHRpbWUgPT0gMCwKICBjb25kaXRpb24gPT0gY29uZCkgJT4lCiAgCiAgIyBmaWx0ZXIgZm9yIGRpZmZlcmVudGlhbCBmaXRuZXNzIChERikgZ2VuZXMKICBmaWx0ZXIoIWJldHdlZW4od21lYW5fZml0bmVzcywgLTIuMCwgMi4wKSwgIWlzLm5hKGxvY3VzKSkgJT4lCiAgCiAgIyBwZXJmb3JtIEtFR0cgZW5yaWNobWVudAogIHB1bGwobG9jdXMpICU+JSBrZWdnYShzcGVjaWVzLktFR0cgPSAic3luIikgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbiA9IGNvbmQpCn0pICU+JSBiaW5kX3Jvd3MKCmhlYWQoZGZfa2VnZ19lbnJpY2htZW50KQpgYGAKCk5vdyB3ZSB2aXN1YWxpemUgdGhlIHBhdGh3YXlzIHRoYXQgYXJlIG1vc3QgZW5yaWNoZWQgZm9yIERGIGdlbmVzLiBJdCB0dXJucyBvdXQgdGhhdCByaWJvc29tYWwgcHJvdGVpbnMgYXJlIGV4dHJlbWVseSBkZXBsZXRlZCBhbmQgdGhlcmVmb3JlIHNjb3JlIGhpZ2ggb24gdGhlIG5lZ2F0aXZlIGxvZzEwIHAtdmFsdWUgZm9yIHBhdGh3YXkgZW5yaWNobWVudC4KCmBgYHtyLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNS41fQpkZl9rZWdnX2VucmljaG1lbnQgJT4lCiAgcmVuYW1lKGtlZ2dfcGF0aHdheSA9IFBhdGh3YXkpICU+JQogIGdyb3VwX2J5KGtlZ2dfcGF0aHdheSkgJT4lIGZpbHRlcihOID49IDIwKSAlPiUKICBzZWxlY3Qoa2VnZ19wYXRod2F5LCBjb25kaXRpb24sIFAuREUpICU+JQogIG11dGF0ZShsb2cxMF9wX3ZhbHVlID0gLWxvZzEwKFAuREUpLCAua2VlcCA9ICJ1bnVzZWQiKSAlPiUKICBtdXRhdGUoa2VnZ19wYXRod2F5ID0gcGFzdGUwKHN0cl9zdWIoa2VnZ19wYXRod2F5LCAxLCAyNSksICIuLiIpKSAlPiUKICAKICAjIG1ha2UgY29ycmVsYXRpb24gcGxvdAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBjb25kaXRpb24sIHZhbHVlc19mcm9tID0gbG9nMTBfcF92YWx1ZSkgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJrZWdnX3BhdGh3YXkiKSAlPiUgYXMubWF0cml4ICU+JQogIGNvcnJwbG90KGlzLmNvcnIgPSBGQUxTRSwgdGwuY29sID0gZ3JleSgwLjUpLCB0bC5jZXggPSAwLjgsCiAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGN1c3RvbV9jb2xvcnNbYygxLDUsMildKSgxMCksIGNvbC5saW0gPSBjKDAsIDIwKSkKYGBgCgoKIyBVbnN1cGVydmlzZWQgY2x1c3RlcmluZyBvZiBnZW5lcwoKIyMgQ2x1c3RlciBnZW5lcyBieSBzaW1pbGFyaXR5CgpXZSBjYW4gbG9hZCBhIGdlbmVyYWxpemVkIGB0aWR5dmVyc2VgIGZyaWVuZGx5IGZ1bmN0aW9uIHRvIGNsdXN0ZXIgYSBuYW1lIHZhcmlhYmxlIGJ5IGEgdmFsdWUsIGdyb3VwZWQgYnkgb25lIG9yIG1vcmUgZ3JvdXBpbmcgdmFyaWFibGVzLiBGb3IgZXhhbXBsZSwgY2x1c3RlciBnZW5lcyAobmFtZSkgYnkgZml0bmVzcyAodmFsdWUpIG92ZXIgc2V2ZXJhbCBjb25kaXRpb25zIChncm91cHMpLiBUaGUgb3V0cHV0IGlzIGEgZmFjdG9yIHdpdGggcmUtb3JkZXJlZCBsZXZlbHMuCgpgYGB7cn0KaWYgKCEiUnRvb2xzIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkgewogIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigibS1qYWhuL1J0b29scyIpCn0KYGBgCgpIZWF0IG1hcCBvZiBmaXRuZXNzIGZvciAqYWxsIGdlbmVzIGFuZCBhbGwgY29uZGl0aW9ucyouCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDIuMn0KbGlicmFyeShSdG9vbHMpCgpwbG90X2hlYXRtYXBfYWxsIDwtIGRmX2dlbmUgJT4lIGZpbHRlcih0aW1lID09IDAsICFpcy5uYShsb2N1cykpICU+JQogIG11dGF0ZShsb2N1cyA9IGZjdF9jbHVzdGVyKGxvY3VzLCBjb25kaXRpb24sIHdtZWFuX2ZpdG5lc3MpKSAlPiUKICBtdXRhdGUod21lYW5fZml0bmVzcyA9IHdtZWFuX2ZpdG5lc3MgJT4lIHJlcGxhY2UoLiwgLiA+IDQsIDQpICU+JSByZXBsYWNlKC4sIC4gPCAtNCwgLTQpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2N1cywgeSA9IGNvbmRpdGlvbiwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9ICJyaWdodCIpICsKICBsYWJzKHggPSBwYXN0ZTAoImdlbmVzICgiLCBsZW5ndGgodW5pcXVlKGRmX2dlbmUkbG9jdXMpKSwiKSIpLCB5ID0gIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTQsIDQpKQoKcHJpbnQocGxvdF9oZWF0bWFwX2FsbCkKYGBgCgoKTm93IHdlIGNhbiBwbG90IF9hbGxfIGdlbmVzLCBhIHN1YnNldCB3aXRoIF9vbmx5IHNpZ25pZmljYW50IGdlbmVzXywgYW5kIGEgZGVuZHJvZ3JhbSBmb3IgY2x1c3RlcmluZy4gVGhlIHJlc3VsdCBpcyBoYXJkIHRvIGludGVycHJldC4gV2l0aCBzb21lIGV4Y2VwdGlvbnMsIG1vc3QgZ2VuZXMgYXJlIGdyb3VwZWQgaW4gYnJvYWQgdW5zcGVjaWZpYyBjbHVzdGVycyB0aGF0IGRvIG5vdCByZXZlYWwgY2xlYXIgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRyZWF0bWVudCB2YXJpYWJsZXMgYW5kIGZpdG5lc3Mgb3V0Y29tZS4KCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDR9CiMgcHJlcGFyZSBuZXcgZGYgYW5kIHBsb3QgaGVhdG1hcApkZl9oZWF0bWFwIDwtIGRmX2dlbmUgJT4lIGZpbHRlcih0aW1lID09IDAsICFpcy5uYShsb2N1cykpICU+JQogIGdyb3VwX2J5KGxvY3VzKSAlPiUKICBmaWx0ZXIoYW55KCFiZXR3ZWVuKHdtZWFuX2ZpdG5lc3MsIC00LCA0KSAmIHBfZml0bmVzc19hZGogPCAwLjAxKSkgJT4lIHVuZ3JvdXAgJT4lCiAgbXV0YXRlKGxvY3VzID0gZmN0X2NsdXN0ZXIobG9jdXMsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gOCwgOCkgJT4lIHJlcGxhY2UoLiwgLiA8IC04LCAtOCkpCgpwbG90X2hlYXRtYXBfc2lnIDwtIGRmX2hlYXRtYXAgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbG9jdXMsIHkgPSBjb25kaXRpb24sIGZpbGwgPSB3bWVhbl9maXRuZXNzKSkgKwogIGdlb21fdGlsZSgpICsgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3MgPSAicmlnaHQiKSArCiAgbGFicyh4ID0gcGFzdGUwKCJnZW5lcyAoIiwgbGVuZ3RoKHVuaXF1ZShkZl9oZWF0bWFwJGxvY3VzKSksIikiKSwgeSA9ICIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKGN1c3RvbV9jb2xvcnNbMV0sIGdyZXkoMC45KSwgY3VzdG9tX2NvbG9yc1syXSksCiAgICBsaW1pdHMgPSBjKC04LCA4KSkKCiMgcHJlcGFyZSBkaXN0IG9iamVjdCBmb3IgY2x1c3RlcmluZyBhbmQgcGxvdCBkZW5kCmRpc3RfaGVhdG1hcCA8LSBkZl9oZWF0bWFwICU+JSBzZWxlY3QobG9jdXMsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGNvbmRpdGlvbiwgdmFsdWVzX2Zyb20gPSB3bWVhbl9maXRuZXNzKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXModmFyID0gImxvY3VzIikgJT4lIGFzLm1hdHJpeCAlPiUKICBkaXN0CgpwbG90X2NsdXN0ZXJfZGVuZCA8LSBkaXN0X2hlYXRtYXAgJT4lCiAgaGNsdXN0KG1ldGhvZCA9ICJ3YXJkLkQyIikgJT4lIGFzLmRlbmRyb2dyYW0gJT4lCiAgc2V0KCJicmFuY2hlc19rX2NvbCIsIGN1c3RvbV9jb2xvcnNbMTo1XSwgayA9IDUpICU+JQogIHNldCgiYnJhbmNoZXNfbHdkIiwgMC41KSAlPiUKICBhcy5nZ2RlbmQgJT4lCiAgZ2dwbG90KGxhYmVscyA9IEZBTFNFKQoKIyBhcnJhbmdlIGJvdGggb24gc2FtZSBwbG90CmdnYXJyYW5nZShucm93ID0gMiwgaGVpZ2h0cyA9ICBjKDAuNSwgMC41KSwKICBwbG90X2NsdXN0ZXJfZGVuZCArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAuMSwgMC4wOSwgLTAuMTUsIDAuMTM2KSwibnBjIikpLAogIHBsb3RfaGVhdG1hcF9zaWcKKQpgYGAKCiMjIEdlbmUgc2ltaWxhcml0eSBieSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gbWV0aG9kcwoKV2UgdXNlIHR3byBkaWZmZXJlbnQgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIG1ldGhvZHMsICoqbk1EUyoqIGFuZCAqKnQtU05FKiouIFdlIGNhbiBjaGVjayBpZiB0aGVzZSBtZXRob2RzIHJlcHJvZHVjZSB0aGUgY2x1c3RlcmluZyBmb3IgdGhlIHNpZ25pZmljYW50bHkgcmVndWxhdGVkIGdlbmVzIHByb2R1Y2VkIHdpdGggYGhjbHVzdGAuIEFuYWx5c2lzIHNob3dzIHRoYXQgdGhlIHNtYWxsIGNsdXN0ZXJzIGFyZSBtb3JlIHN0cm9uZ2x5IHNlcGFyYXRlZCBmcm9tIHRoZSByZXN0LgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA0fQojIHNldCBhIHNlZWQgdG8gb2J0YWluIHNhbWUgcGF0dGVybiBmb3Igc3RvY2hhc3RpYyBtZXRob2RzCnNldC5zZWVkKDMyMSkKCiMgcnVuIG5NRFMgYW5hbHlzaXMKTk1EUyA8LSAgZGlzdF9oZWF0bWFwICU+JSBtZXRhTURTCmRmX25tZHMgPC0gTk1EUyRwb2ludHMgJT4lIGFzX3RpYmJsZShyb3duYW1lcyA9ICJsb2N1cyIpICU+JQogIGxlZnRfam9pbihlbmZyYW1lKG5hbWUgPSAibG9jdXMiLCB2YWx1ZSA9ICJjbHVzdGVyIiwKICAgIGN1dHJlZW9yZChoY2x1c3QoZGlzdF9oZWF0bWFwLCBtZXRob2QgPSAid2FyZC5EMiIpLCBrID0gNSkpKQoKIyBydW4gdC1TTkUgYW5hbHlzaXMKU05FIDwtIGRpc3RfaGVhdG1hcCAlPiUgdHNuZShtYXhfaXRlciA9IDUwMCwgcGVycGxleGl0eSA9IDgpCmRmX3RzbmUgPC0gU05FICU+JSBzZXROYW1lcyhjKCJ4IiwgInkiKSkgJT4lIGFzX3RpYmJsZSAlPiUKICBtdXRhdGUobG9jdXMgPSB1bmlxdWUoZGZfaGVhdG1hcCRsb2N1cykpICU+JQogIGxlZnRfam9pbihlbmZyYW1lKG5hbWUgPSAibG9jdXMiLCB2YWx1ZSA9ICJjbHVzdGVyIiwKICAgIGN1dHJlZW9yZChoY2x1c3QoZGlzdF9oZWF0bWFwLCBtZXRob2QgPSAid2FyZC5EMiIpLCBrID0gNSkpKQoKcGxvdF9ubWRzIDwtIGRmX25tZHMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gTURTMSwgeSA9IE1EUzIsIGNvbG9yID0gZmFjdG9yKGNsdXN0ZXIpKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsgbGFicyh0aXRsZSA9ICJuTURTIikgKwogIGN1c3RvbV90aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuNzgpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnMpCgpwbG90X3RzbmUgPC0gZGZfdHNuZSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWMSwgeSA9IFYyLCBjb2xvciA9IGZhY3RvcihjbHVzdGVyKSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArIGxhYnModGl0bGUgPSAidC1TTkUiKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC43OCkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX2NvbG9ycykKCmdnYXJyYW5nZShuY29sID0gMiwgcGxvdF9ubWRzLCBwbG90X3RzbmUpCmBgYAoKIyMgRml0IG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVscwoKV2UgY2FuIGZpbmQgY2x1c3RlcnMgb2YgZ2VuZXMgd2l0aCBzaW1pbGFyIGZpdG5lc3MsIGJ1dCBpdCBpcyBhbHNvIGltcG9ydGFudCB0byBpZGVudGlmeSBfd2h5XyB0aGV5IGNsdXN0ZXIgdG9nZXRoZXIuIEluIG9yZGVyIHRvIGZpbmQgb3V0IF93aGljaCB2YXJpYWJsZXNfIGRldGVybWluZSB0aGUgZml0bmVzcyBvdXRjb21lIG9mIGEgZ2VuZSwgd2UgY2FuIHBlcmZvcm0gKiptdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbioqLiBFYWNoIGdlbmUgbmVlZHMgdG8gaGF2ZSBmaXRuZXNzIG91dGNvbWVzIGFubm90YXRlZCB3aXRoIHRoZSBkaWZmZXJlbnQgKG1peGVkKSB2YXJpYWJsZXMgYGNhcmJvbmAsIGBsaWdodGAsIGB0cmVhdG1lbnRgLiBUaGUgbGF0dGVyIGNhbiBiZSBzdWJkaXZpZGVkIGluIGluZGl2aWR1YWwgdHJlYXRtZW50IGNvbHVtbnMgZ2x1Y29zZSwgRENNVSwgZmx1Y3R1YXRpbmcgbGlnaHQsIGFuZCBzbyBvbi4gTXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gZml0cyBhIGxpbmVhciBtb2RlbCBvZiB0aGUgZm9sbG93aW5nIGZvcm0gdG8gdGhlIGRhdGE6CgpgcmVzcG9uc2UgfiBpbnRlcmNlcHQgKyBwcmVkaWN0b3IgQSB4IHNsb3BlIEEgKyBwcmVkaWN0b3IgQiB4IHNsb3BlIEIgeCAuLi5gCgpIZXJlLCBgZml0bmVzc2AgaXMgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLCB0aGUgZGlmZmVyZW50IGNvbmRpdGlvbnMgYXJlIHRoZSBwcmVkaWN0b3JzLiBJdCBpcyBpbXBvcnRhbnQgdG8gY29udmVydCB0aGUgY2F0ZWdvcmljYWwgcHJlZGljdG9ycyBpbnRvIChudW1lcmljYWwpIGR1bW15IHZhcmlhYmxlcy4gVGhlbiBmb3IgZWFjaCBpbmRpdmlkdWFsIGdlbmUsIG11bHRpcGxlIGxpbmVhciBtb2RlbHMgYXJlIGZpdHRlZCBhbmQgdGhlIHBvd2VyIG9mIGVhY2ggcHJlZGljdG9yIHZhcmlhYmxlIHRvIHByZWRpY3QgdGhlIHJlc3BvbnNlIGlzIGV4dHJhY3RlZC4KCmBgYHtyfQojIGZpeGVkIG1vZGVsIHdpdGggNiBwcmVkaWN0b3IgdmFyaWFibGVzIC0tIGR5bmFtaWMgbGF5b3V0IHdvdWxkIAojIGJlIGJldHRlciBpbiBmdXR1cmUKZml0X2xpbnJlZyA8LSBmdW5jdGlvbih5LCB4MSwgeDIsIHgzLCB4NCwgeDUsIHg2KXsKICBmaXQgPC0gbG0oeSB+IHgxICsgeDIgKyB4MyArIHg0ICsgeDUgKyB4NikKICBjKGNvZWZmaWNpZW50cyhmaXQpLCBzdW1tYXJ5KGZpdCkkY29lZmZpY2llbnRzWywgNF0sCiAgICBzdW1tYXJ5KGZpdCkkci5zcXVhcmVkKQp9CgojIHJlY29kZSBjYXRlZ29yaWNhbCB0byBudW1lcmljYWwgKGR1bW15KSB2YXJpYWJsZXMKZGZfbGlucmVnIDwtIGRmX21haW4gJT4lCiAgZmlsdGVyKCFpcy5uYShsb2N1cykpICU+JQogIHNlbGVjdChsb2N1cywgY2FyYm9uLCBsaWdodCwgdHJlYXRtZW50LCBmaXRuZXNzKSAlPiUgZGlzdGluY3QgJT4lCiAgCiAgbXV0YXRlKAogICAgY2FyYm9uID0gcmVjb2RlKGNhcmJvbiwgYEhDYCA9IDEsIGBMQ2AgPSAwKSwKICAgIGxpZ2h0ID0gcmVjb2RlKGxpZ2h0LCBgTExgID0gMCwgYElMYCA9IDAuNSwgYEhMYCA9IDEpKSAlPiUKICBtdXRhdGUoZHVtbXkgPSAxLCB0cmVhdG1lbnQgPSByZXBsYWNlKHRyZWF0bWVudCwgdHJlYXRtZW50ID09ICIiLCAiLSIpKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdHJlYXRtZW50LCB2YWx1ZXNfZnJvbSA9IGR1bW15LCB2YWx1ZXNfZmlsbCA9IDApICU+JQogIG11dGF0ZShgK0dgID0gYCtHYCArIGArRCwgK0dgKSAlPiUgcmVuYW1lKGArRGAgPSBgK0QsICtHYCkgJT4lIHNlbGVjdCgtYC1gKSAlPiUKICAjIGZpdCBtb2RlbAogIGdyb3VwX2J5KGxvY3VzKSAlPiUKICBzdW1tYXJpemUoY29lZmZpY2llbnQgPSBmaXRfbGlucmVnKGZpdG5lc3MsIGNhcmJvbiwgbGlnaHQsIGAtTmAsIGArRkxgLCBgK0dgLCBgK0RgKSwKICAgIC5ncm91cHMgPSAia2VlcCIpICU+JQogIG11dGF0ZSh0cmVhdG1lbnQgPSBjKHJlcChjKCJpbnRlcmNlcHQiLCAiY2FyYm9uIiwgImxpZ2h0IiwgIi1OIiwgIitGTCIsICIrRyIsICIrRCIpLCAyKSAlPiUgCiAgICBwYXN0ZTAocmVwKGMoIiIsICJwdmFsXyIpLCBlYWNoID0gNyksIC4pLCAicl9zcXVhcmVkIikpCmBgYAoKUmVzaGFwZSB0aGUgbG9uZyBkYXRhIGZyYW1lIGludG8gYSB3aWRlIGZvcm1hdCwgd2l0aCBzbG9wZSwgaW50ZXJjZXB0IGFuZCByLXNxdWFyZWQgaW4gc2VwYXJhdGUgY29sdW1ucy4KQWRkIGdlbmUgbmFtZXMgYW5kIHRTTkUgY29vcmRpbmF0ZXMuCgpgYGB7cn0KZGZfbGlucmVnIDwtIGRmX2xpbnJlZyAlPiUKICBsZWZ0X2pvaW4oc2VsZWN0KGRmX2dlbmUsIGxvY3VzLCBzZ1JOQV90YXJnZXQpICU+JSBkaXN0aW5jdCwgYnkgPSAibG9jdXMiKSAlPiUKICBtdXRhdGUoY29lZmZfdHlwZSA9IGNhc2Vfd2hlbigKICAgIHRyZWF0bWVudCA9PSAicl9zcXVhcmVkIiB+ICJyX3NxdWFyZWQiLAogICAgc3RyX2RldGVjdCh0cmVhdG1lbnQsICJwdmFsIikgfiAicF92YWx1ZSIsCiAgICBUUlVFIH4gInNsb3BlIgogICkpICU+JQogIG11dGF0ZSh0cmVhdG1lbnQgPSBzdHJfcmVtb3ZlKHRyZWF0bWVudCwgInB2YWxfIikgJT4lIHN0cl9yZXBsYWNlKCJyX3NxdWFyZWQiLCAiaW50ZXJjZXB0IikpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSAiY29lZmZfdHlwZSIsIHZhbHVlc19mcm9tID0gImNvZWZmaWNpZW50IikgJT4lCiAgZ3JvdXBfYnkobG9jdXMpICU+JSBmaWxsKHJfc3F1YXJlZCkKYGBgCgpOb3cgd2UgY2FuIG92ZXJsYXkgdGhlIGluZm9ybWF0aW9uIG9mIHRoZSBiZXN0IHByZWRpY3RvciB2YXJpYWJsZSBvbiB0aGUgY2x1c3RlciBtYXAgcHJvZHVjZWQgYnkgdFNORSwgZm9yIGV4YW1wbGUsIGFuZCB0aGlzIHdheSBpZGVudGlmeSBncm91cHMgb2YgZ2VuZXMgcmVndWxhdGVkIGluIGEgc2ltaWxhciBkZWdyZWUsIGJ5IHNpbWlsYXIgdmFyaWFibGVzLgoKYGBge3IsIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQgPSAxMn0KcGxvdF90c25lX2xpbnJlZyA8LSBkZl9saW5yZWcgJT4lCiAgaW5uZXJfam9pbihkZl90c25lLCBieSA9ICJsb2N1cyIpICU+JQogIGZpbHRlcih0cmVhdG1lbnQgIT0gImludGVyY2VwdCIpICU+JQogIGFycmFuZ2UodHJlYXRtZW50LCBhYnMoc2xvcGUpKSAlPiUKICBtdXRhdGUoc2dSTkFfdGFyZ2V0ID0gaWZfZWxzZShwX3ZhbHVlIDw9IDAuMDEsIHNnUk5BX3RhcmdldCwgIiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWMSwgeSA9IFYyLCBzaXplID0gYWJzKHNsb3BlKSwKICAgIGNvbG9yID0gY3V0KHBfdmFsdWUsIGJyZWFrcyA9IGMoMCwgMC4wMDEsIDAuMDEsIDAuMDUsIDEpKSwgbGFiZWwgPSBzZ1JOQV90YXJnZXQpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNykgKwogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMywgbWF4Lm92ZXJsYXBzID0gNTAsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gcGFzdGUwKCJ0LVNORSBjbHVzdGVyaW5nIG9mICIsIG5yb3coZGZfdHNuZSksICIgZ2VuZXMgd2l0aCBhYnNvbHV0ZSBmaXRuZXNzIHNjb3JlID4gNCIpLAogICAgc3VidGl0bGUgPSBwYXN0ZTAoImRvdCBzaXplIGVuY29kZXMgZWZmZWN0IG9mIHZhcmlhYmxlIGZyb20gbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3NvbiwgIiwKICAgICAgImRvdCBjb2xvciBlbmNvZGVzIHAtdmFsdWUiKSkgKwogIGN1c3RvbV90aGVtZShhc3BlY3QgPSAxKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoY29sb3JSYW1wUGFsZXR0ZShjKCIjRTcyOThBIiwgIiNFNkFCMDIiKSkoMyksICIjQjNCM0IzIikpICsKICAjc2NhbGVfY29sb3JfZ3JhZGllbnRuKGxpbWl0cyA9IGMoLTUsIDUpLCBjb2xvdXJzID0gY3VzdG9tX2NvbG9yc1sxOjJdKSArCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLCA2KSkgKwogIGZhY2V0X3dyYXAoIH4gdHJlYXRtZW50LCBuY29sID0gMikKCnByaW50KHBsb3RfdHNuZV9saW5yZWcpCmBgYAoKYGBge3IsIGVjaG8gPSBGQUxTRX0Kc2F2ZV9wbG90KHBsID0gcGxvdF90c25lX2xpbnJlZywgcGF0aCA9ICIuLi9maWd1cmVzLyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gMTIpCmBgYAoKVGhpcyBzdHJhdGVneSByZXZlYWxzIGEgbGlzdCBvZiBpbnRlcmVzdGluZyBjb25kaXRpb24tc3BlY2lmaWMgZ2VuZXM6CgotIEZsdWN0dWF0aW5nIGxpZ2h0OgogIC0gYHNsbDAyMTdgIC0gUHV0YXRpdmUgZGlmbGF2aW4gZmxhdm9wcm90ZWluIEEyIChkZmEyIC8gRmx2NCkKICAtIGBzbGwwMjE4YCAtIFB1dGF0aXZlIGRpZmxhdmluIGZsYXZvcHJvdGVpbiBhc3NvY2lhdGVkIHByb3RlaW4KICAtIFB1dGF0aXZlIGRpZmxhdmluIGZsYXZvcHJvdGVpbnMgYHNsbDE1MjFgIChGbHYxKSBhbmQgYHNsbDA1NTBgIChGbHYzKSBzaG93IGludmVyc2UgYnV0IEZMKyBzcGVjaWZpYwogICAgZml0bmVzcyBwYXR0ZXJuIGJ1dCB3ZXJlIG5vdCBpbmNsdWRlZCBpbiBmaWd1cmUgYmVjYXVzZSBvZiBsb3dlciBzaWduaWZpY2FuY2UvRkMKLSBNaXhvdHJvcGh5OgogIC0gYHNsbDA1OTNgIC0gZ2xrLCBnbHVjb2tpbmFzZSwgY2F0YWx5emVzIFAteWxhdGlvbiBvZiBHbGMgdG8gRzZQCiAgLSBgc3NsMzM2NGAgLSBDUDEyIHNtYWxsIHByb3RlaW4sIHN0cm9uZ2x5IGludGVyYWN0cyB3aXRoIFJiY1gsIFJiY1IsIFByay4gTm92ZWwgQy1tZXRhYm9saXNtIHJlZ3VsYXRvcgotIExpZ2h0OgogIC0gYHNzcjIxNDJgIHljZjE5LCBzaG9ydCB1bmtub3duIHByb3RlaW4sIGludGVyYWN0cyB3aXRoIHBzYk8gYW5kIFRhdCBtZW1icmFuZSBwcm90ZWluIGluc2VydGlvbiBzeXN0ZW0sCiAgLSBgc2xyMDk2M2Agc2lyLCBzdWxmaXRlIHJlZHVjdGFzZSwgZmVycmVkb3hpbiBIMk8gKyBIUyArIGZlcnJlZG94aW4gPC0+IEgrICsgcmVkdWNlZCBmZXJyZWRveGluICsgc3VsZml0ZSwKICAgIHN0cm9uZ2x5IGludGVyYWN0cyB3aXRoIG90aGVyIHByb3RlaW5zIGluIHN1bGZ1ciBtZXRhYm9saXNtLCBzcGVjaWZpY2FsbHkgcmVsYXRlZCB0byBjb2ZhY3RvciBiaW9zeW50aGVzaXMsIAogICAgY29iYWxhbWluICh2aXRhbWluIEIxMikgYW5kICAgIHNpcm9oZW1lCi0gTGlnaHQsIG1peG90cm9waHksIGhldGVyb3Ryb3BoeTogY2x1c3RlciBvZiBwaG90b3N5bnRoZXNpcyByZWxhdGVkIGdlbmVzIGluY3JlYXNlIGZpdG5lc3Mgd2hlbiBLT2VkOiBhcGNBLEQsRSwgcHNiQixDLEQKLSBDYXJib246CiAgLSBgc2xsMDIxN2AgUHV0YXRpdmUgZGlmbGF2aW4gZmxhdm9wcm90ZWluIEEyIChkZmEyKSwgS08gbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggZml0bmVzcyB3aXRoIEMsIHBvc2l0aXZlIHdpdGggK0ZMCiAgLSBgc2xsMDIxOGAgc2FtZSBiZWhhdmlvciBhcyBkZmEyLCBpbnRlcmFjdHMgd2l0aCBkZmEyLDQsIGNvbnRyaWJ1dGVzIHRvIFBTSUkgc3RhYmlsaXphdGlvbiwgCiAgICAgW0JlcnNhbmluaSBldCBhbC4sIDIwMTddKGh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvMjc5Mjg4MjQvKS4gTmVnYXRpdmUgZml0bmVzcyBzY29yZQogICAgIHByb2JhYmx5IHNpZGUgZWZmZWN0IG9mIGRvd25zdHJlYW0gS0Qgb2YgYHNsbDAyMTlgIChGbHYyKS4gVGhhdCBzaG93cyBlc3NlbnRpYWxseSBzYW1lIHBhdHRlcm4gYXMKICAgICBgc2xsMDIxN2AgYW5kIGBzbGwwMjE4YCBidXQgd2Vha2VyIGVmZmVjdCBvbiBmaXRuZXNzLgoKIyMgTGlzdCBvZiBnZW5lcyB3aXRoIHN0cm9uZyBmaXRuZXNzIGNvcnJlbGF0aW9uCgpUaGUgdGFibGUgd2l0aCBsaW5lYXIgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgYW5kIHAtdmFsdWVzIGlzIHJlc2hhcGVkIHRvIHdpZGUgZm9ybWF0IGZvciBiZXR0ZXIgcmVhZGFiaWxpdHkuIFRoZSBga2FibGVFeHRyYWAgcGFja2FnZSBpcyB1c2VkIHRvIGNvbG9yIGNlbGxzIGZvciBlYXNpZXIgcmVjb2duaXRpb24uIFRoZW4gd2Ugc3Vic2V0IHRoZSB0YWJsZSBmb3IgZWFjaCB0cmVhdG1lbnQgaW4gb3JkZXIgdG8gc3BvdCB0aGUgbW9zdCBpbnRlcmVzdGluZyBnZW5lcy4KCmBgYHtyfQpkZl9saW5yZWdfZmlsdGVyZWQgPC0gZGZfbGlucmVnICU+JQogIGZpbHRlcih0cmVhdG1lbnQgIT0gImludGVyY2VwdCIpICU+JQogIGdyb3VwX2J5KGxvY3VzKSAlPiUKICBmaWx0ZXIoYW55KGFicyhzbG9wZSkgPj0gMiAmIHBfdmFsdWUgPD0gMC4wMSkpICU+JQogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIH4gcm91bmQoLiwgMykpKSAlPiUKICBwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSAidHJlYXRtZW50IiwKICAgIHZhbHVlc19mcm9tID0gYygic2xvcGUiLCAicF92YWx1ZSIpLAogICAgbmFtZXNfcmVwYWlyID0gZnVuY3Rpb24oeCkge3N0cl9yZW1vdmUoeCwgInNsb3BlXyIpfQogICkKICAKY29sb3JfdGFibGUgPC0gZnVuY3Rpb24oZGYsIHZhcmlhYmxlKSB7CiAgZmlsdGVyKGRmLCBhYnMoLmRhdGFbW3ZhcmlhYmxlXV0pID4gMikgJT4lCiAgZmlsdGVyKGlmX2FueShwYXN0ZTAoInBfdmFsdWVfIiwgdmFyaWFibGUpLCB+IC4gPD0gMC4wMSkpICU+JQogIHNlbGVjdChtYXRjaGVzKCJeKHNnfGxvY3xyX3N8Y2FyYnxsaWdodHxcXC18XFwrKSIpIHwKICAgIGFsbF9vZihwYXN0ZTAoInBfdmFsdWVfIiwgdmFyaWFibGUpKSkgJT4lCiAgYXJyYW5nZShkZXNjKC5kYXRhW1t2YXJpYWJsZV1dKSkgJT4lCiAgbXV0YXRlKGFjcm9zcygzOjgsIH4gY2VsbF9zcGVjKC4sICJodG1sIiwgY29sb3IgPSAid2hpdGUiLAogICAgICBiYWNrZ3JvdW5kID0gc3BlY19jb2xvciguLCBvcHRpb24gPSAiRSIsIHNjYWxlID0gYygtNS41LCA1LjUpKSwKICAgICAgYm9sZCA9IFRSVUUpKSkgJT4lCiAga2JsKGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lCiAga2FibGVfcGFwZXIoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRikKfQpgYGAKCgpgYGB7cn0KZGZfbGlucmVnX2ZpbHRlcmVkICU+JSBjb2xvcl90YWJsZSgiY2FyYm9uIikKYGBgCgpgYGB7cn0KZGZfbGlucmVnX2ZpbHRlcmVkICU+JSBjb2xvcl90YWJsZSgibGlnaHQiKQpgYGAKCgpgYGB7cn0KZGZfbGlucmVnX2ZpbHRlcmVkICU+JSBjb2xvcl90YWJsZSgiK0ZMIikKYGBgCgpgYGB7cn0KZGZfbGlucmVnX2ZpbHRlcmVkICU+JSBjb2xvcl90YWJsZSgiK0ciKQpgYGAKCmBgYHtyfQpkZl9saW5yZWdfZmlsdGVyZWQgJT4lIGNvbG9yX3RhYmxlKCIrRCIpCmBgYAoKKipVbmtub3duIC8gdW5jaGFyYWN0ZXJpemVkIGdlbmVzKioKCkJhc2VkIG9uIHRoZSBtdWx0aXBsZSBsaW5lYXIgbW9kZWwgY29ycmVsYXRpb25zLCB3ZSBjYW4gdHJ5IHRvIGV4dHJhY3QgYSBzaG9ydGxpc3Qgb2YgdGhlIG1vc3QgaW50ZXJlc3RpbmcgKipoeXBvdGhldGljYWwgZ2VuZXMqKi4gVGhlc2UgY291bGQgd2FycmFudCBmdXJ0aGVyIGludmVzdGlnYXRpb25zLgoKYGBge3J9CmRmX3Vua25vd25faGl0cyA8LSBkZl9saW5yZWdfZmlsdGVyZWQgJT4lCiAgbGVmdF9qb2luKGRmX3VuaXByb3QsIGJ5ID0gImxvY3VzIikgJT4lCiAgIyBmaWx0ZXIgYnkgbmFtZTogb25seSB1bmtub3duIHByb3RlaW5zCiAgZmlsdGVyKAogICAgIWxvY3VzICVpbiUgYygic3NsMzM2NCIsICJzbGwwMjE4IiwgInNsbDE3MzQiLCAic2xyMTMwMiIpLAogICAgaXMubmEoZ2VuZV9uYW1lX3Nob3J0KSwKICAgIGlzLm5hKHBhdGh3YXkpLAogICAgc3RyX2RldGVjdChwcm90ZWluLCAiW2EtekEtWl17M31bMC05XXs0fSBwcm90ZWlufFVuY2hhcmFjdGVyaXplZCIpKSAlPiUKICBzZWxlY3QobG9jdXM6dW5pcHJvdF9pZCkgJT4lCiAgcm93d2lzZSgpICU+JSBtdXRhdGUobWluX3B2YWwgPSBtaW4oY19hY3Jvc3MobWF0Y2hlcygicF92YWx1ZV8iKSkpKSAlPiUKICBhcnJhbmdlKG1pbl9wdmFsKSAlPiUKICB1bmdyb3VwICU+JSBzbGljZSgxOjEwKQoKZGZfdW5rbm93bl9oaXRzICU+JQogIG11dGF0ZShhY3Jvc3MoNDo5LCB+IGNlbGxfc3BlYyguLCAiaHRtbCIsIGNvbG9yID0gIndoaXRlIiwKICAgICAgYmFja2dyb3VuZCA9IHNwZWNfY29sb3IoLiwgb3B0aW9uID0gIkUiLCBzY2FsZSA9IGMoLTUuNSwgNS41KSksCiAgICAgIGJvbGQgPSBUUlVFKSkpICU+JQogIGtibChmb3JtYXQgPSAiaHRtbCIsIGVzY2FwZSA9IEYpICU+JQogIGthYmxlX3BhcGVyKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpCmBgYAoKVGhlIHRhYmxlcyBhYm92ZSBzaG93IGdlbmVzIHdob3NlIGZpdG5lc3MgaXMgbW9zdCBzaWduaWZpY2FudGx5IGNvcnJlbGF0ZWQgd2l0aCBvbmUgb2YgdGhlIHRyZWF0bWVudHMuClRoZSB0YWJsZSB3aXRoIHVua25vd24gZ2VuZXMgaXMgZnVydGhlciB1c2VkIHRvIHBsb3QgZml0bmVzcyBwZXIgY29uZGl0aW9uIGFzIGEgbGluZSBwbG90LCBpbiBvcmRlciB0byBpbnNwZWN0IHRoZSB0cmVuZHMgZnJvbSBmaXR0aW5nIHRoZSBtdWx0aXBsZSBsaW5lciByZWdyZXNzaW9uIG1vZGVscy4gTm90ZSB0aGF0IHRoaXMgbGlzdCBvZiBnZW5lcyB3YXMgZGVyaXZlZCBmcm9tIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uLiBOb3RlIHRoYXQgdGhlIG9idGFpbmVkIHAtdmFsdWUgdXNlZCBmb3IgZmlsdGVyaW5nIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBmaXRuZXNzIHAtdmFsdWUgZm9yIHRoZSBleHBlcmltZW50YWwgY29uZGl0aW9ucy4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KcGxvdF91bmtub3duX2dlbmVzIDwtIGRmX21haW4gJT4lCiAgZmlsdGVyKGxvY3VzICVpbiUgdW5pcXVlKGRmX3Vua25vd25faGl0cyRsb2N1cykpICU+JQogIG11dGF0ZShsb2N1cyA9IGZhY3Rvcihsb2N1cywgdW5pcXVlKGRmX3Vua25vd25faGl0cyRsb2N1cykpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gbG9nMkZvbGRDaGFuZ2UsIGNvbG9yID0gZmFjdG9yKHNnUk5BX2luZGV4KSkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAic2dSTkEgcHJvZmlsZSBmb3IgdG9wIDEwIHVua25vd24gZ2VuZXMgYWNjb3JkaW5nIHRvIE1MUiIsCiAgICBzdWJ0aXRsZSA9ICJNTFIgZmlsdGVyOiBzbG9wZSA+PSAyLCBwLXZhbHVlIDw9IDAuMDEuIENvbG9yIGVuY29kZXMgc2dSTkEgcG9zaXRpb24iKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9yYW5nZSg1KSkgKwogIGZhY2V0X2dyaWQobG9jdXMgfiBjb25kaXRpb24pCiAgCnByaW50KHBsb3RfdW5rbm93bl9nZW5lcykKYGBgCioqU3VtbWFyeSoqCgotIGBzbGwwMDYyYCAtIHNtYWxsIDE0OSBBQSBtZW1icmFuZSBwcm90ZWluLCBwaGVub3R5cGU6IGZpdG5lc3MgbWFpbmx5IGRlY3JlYXNlZCBpbiBMQy1MTCBjb25kaXRpb21zLCBidXQgbm90IG1peG8vcGhvdG9oZXJvdHJvcGh5Ci0gYHNsbDAxNDhgIC0gbGFyZ2UgNzM1IEFBIG1lbWJyYW5lIHByb3RlaW4sIHBoZW5vdHlwZTogZml0bmVzcyBtYWlubHkgZGVjcmVhc2VkIGluIExMIGNvbmRpdGlvbnMsIHJlZ2FyZGxlc3Mgb2YgQywgYnV0IG5vdCBtaXhvL3Bob3RvaGVyb3Ryb3BoeQotIGBzbGwxNTM0YCAtIG1lZGl1bSAzNzggQUEgcHV0YXRpdmUgZ2x5Y29zeWx0cmFuc2ZlcmFzZSwgcGhlbm90eXBlOiBmaXRuZXNzIGRlY3JlYXNlZCBpbiBhbGwgY29uZGl0aW9ucywgYnV0IHByZWRvbWluYW50bHkgbWl4by9waG90b2hlcm90cm9waHkKLSBgc2xyMDQ4M2AgLSBzbWFsbCAxNDkgQUEgbWVtYnJhbmUgcHJvdGVpbiwgcG90ZW50aWFsIHRoeWxha29pZCBwcm90ZWluLCBpbXBsaWNhdGVkIGluIHNjYWZmb2xkaW5nIGZvciBvdGhlciB0aHlsLiBwcm90ZWlucyAoWzFdKGh0dHBzOi8vZXRoZXNlcy53aGl0ZXJvc2UuYWMudWsvNjQ4OC8yL1RoZXNpcyUyMHdpdGglMjBjb3JyZWN0aW9ucy5wZGYpLCBbMl0oaHR0cHM6Ly9lZG9jLnViLnVuaS1tdWVuY2hlbi5kZS8xNjgxOS8xL1NoYW9fTGluLnBkZikpLiBQaGVub3R5cGU6IGZpdG5lc3MgZGVjcmVhc2VkIGluIGFsbCBMIGNvbmRpdGlvbnMsIGJ1dCBub3QgSEMrSEwgYW5kIG1peG8vcGhvdG9oZXJvdHJvcGh5IChzdXBwb3J0cyBpbnZvbHZlbWVudCBpbiBwaG90b3N5bnRoZXNpcykKLSBgc2xyMDY0M2AgLSBtZWRpdW0gNDkzIEFBIHRyYW5zbWVtYnJhbmUgcHJvdGVpbiAoOSBUTSBoZWxpY2VzKSwgcGhlbm90eXBlOiBzdHJvbmcgZGVjcmVhc2UgaW4gSEMrSEwgb25seS4gUHV0YXRpdmUgbWV0YWxsb3Byb3RlYXNlLCBkZWZlY3QgaW4gYWNpZCBhY2NsaW1hdGlvbiwgS08gb3ZlcmV4cHJlc3NlcyBOREggYW5kIENpIHRyYW5zcG9ydGVycyAoWzFdKGh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvMjI5OTc0NjQvKSkKLSBgc2xyMTgxOGAgLSBtZWRpdW0gMjAxIEFBIHByb3RlaW4sIHBoZW5vdHlwZTogZml0bmVzcyBkZWNyZWFzZWQgc3BlY2lmaWNhbGx5IGluIExDK0lMIGFuZCBMQytMTCtGLCBzbyBvbmx5IHVuZGVyIGxpZ2h0IHN0cmVzcy4gUGhlbm90eXBlIHNpbWlsYXIgdG8gdGhlIGZsYXZvZGlpcm9uIHByb3RlaW5zIEZsdjEgYW5kIDMKLSBgc2xyMjA3MGAgLSBtZWRpdW0gMjg0IEFBIHByb3RlaW4sIHVua25vd24gbG9jYWxpemF0aW9uLCBwaGVub3R5cGU6IHN0cm9uZ2x5IGluY3JlYXNlZCBmaXRuZXNzIGluICtHIGNvbmRpdGlvbnMgb25seTsgc2xpZ2h0bHkgZGVjcmVhc2VkIGZpdG5lc3MgaW4gTEMrSUwgYW5kIExDK0xMK0YuIFN1Z2dlc3RzIHRoYXQgcHJvdGVpbiBpcyBhIGJ1cmRlbiBpbiBub24tcGhvdG9zeW50aGV0aWMgY29uZGl0aW9ucywgYnV0IHJlcXVpcmVkIGF0IGhpZ2ggTCB0byBDIHJhdGlvLiBJbXBsaWNhdGVkIGFzIHRoeWxha29pZCBwcm90ZWluIChbMV0oaHR0cHM6Ly9lZG9jLnViLnVuaS1tdWVuY2hlbi5kZS8yODkzNy8pKQotIGBzc3IyMDYyYCAtIHNtYWxsIDg4IEFBIHByb3RlaW4sIGxvY2FsaXphdGlvbiB1bmtub3duLCBwaGVub3R5cGU6IHN0cm9uZ2x5IGluY3JlYXNlZCBmaXRuZXNzIGluICtHIGNvbmRpdGlvbnMgb25seSwgYXMgYWJvdmUuIEltcGxpY2F0ZWQgaW4gcmVndWxhdGlvbiBvZiBsaWdodCBkYXJrIGFkYXB0YXRpb24gYnkgdHJhbnNjcmlwdG9tZSBwcm9maWxpbmcgKFsxXShodHRwczovL29ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS9mdWxsLzEwLjExMTEvbW1pLjE0NzY4KSwgWzJdKGh0dHBzOi8vb25saW5lbGlicmFyeS53aWxleS5jb20vZG9pL2Z1bGwvMTAuMTExMS9tbWkuMTQxMjkpKQotIGBzbHIxMzE1YCAtIG1lZGl1bSAyMDIgQUEgcHJvdGVpbiwgbG9jYWxpemF0aW9uIHVua25vd24sIFVtYTIgcmVzdHJpY3Rpb24gZW5kb251Y2xlYXNlIGRvbWFpbi4gUGhlbm90eXBlOiBmaXRuZXNzIGRlY3JlYXNlZCBzcGVjaWZpY2FsbHkgaW4gTEMrSUwgYW5kIExDK0xMK0YsIHNvIHByZWRvbWluYW50bHkgdW5kZXIgbGlnaHQgc3RyZXNzCi0gYHNsbDE5MTVgIC0gc21hbGwgMTgzIEFBIG1lbWJyYW5lIHByb3RlaW4sIHBoZW5vdHlwZTogZGVjcmVhc2VkIGZpdG5lc3MgcHJlZG9taW5hbnRseSBpbiBMTCBvciBIQyBjb25kaXRpb25zIChIQyxITCBhbmQgK0cpCgoKIyBEaXJlY3QgY29tcGFyaXNvbiBvZiBnZW5lIGZpdG5lc3MKCiMjIEZpdG5lc3Mgb2YgYWxsIGNvbmRpdGlvbnMgdnMgZWFjaCBvdGhlcgoKV2UgY2FuIHBsb3Qgc2VsZWN0ZWQgY29uZGl0aW9ucyBhZ2FpbnN0IGVhY2ggb3RoZXIgYW5kIGFkZCBnZW5lIGxhYmVscyBpbiBvcmRlciB0byBmaW5kIG9yIGNvbmZpcm0gcGFydGljdWxhciBwYXR0ZXJucy4KCmBgYHtyfQptYWtlX2ZpdG5lc3NfcGxvdCA8LSBmdW5jdGlvbihkYXRhLCB2YXJzLCB0aXRsZSA9IE5VTEwpIHsKICAjIHByZXBhcmUgZGF0YSBmb3IgdHdvICB2YXJpYWJsZXMgZWFjaAogIGRhdGEgJT4lIHVuZ3JvdXAgJT4lCiAgICBmaWx0ZXIoY29uZGl0aW9uICVpbiUgdmFycywgc2dSTkFfdHlwZSA9PSAiZ2VuZSIpICU+JQogICAgc2VsZWN0KGxvY3VzLCBzZ1JOQV90YXJnZXQsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykgJT4lIGRpc3RpbmN0ICU+JQogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGNvbmRpdGlvbiwgdmFsdWVzX2Zyb20gPSB3bWVhbl9maXRuZXNzKSAlPiUKICAgIG11dGF0ZSgKICAgICAgZGZpdCA9IGdldCh2YXJzWzFdKSAtIGdldCh2YXJzWzJdKSwKICAgICAgc2lnbmlmaWNhbnQgPSAhYmV0d2VlbihkZml0LCBxdWFudGlsZShkZml0LCBwcm9icyA9IGMoMC4wMDMpKSwKICAgICAgICBxdWFudGlsZShkZml0LCBwcm9icyA9IGMoMC45OTcpKSksCiAgICAgIHNnUk5BX3RhcmdldCA9IGlmX2Vsc2Uoc2lnbmlmaWNhbnQsIHNnUk5BX3RhcmdldCwgIiIpKSAlPiUKICAgIAogICAgIyBwbG90CiAgICBnZ3Bsb3QoYWVzKHggPSBnZXQodmFyc1sxXSksIHkgPSBnZXQodmFyc1syXSksIAogICAgICBjb2xvciA9IHNpZ25pZmljYW50LCBsYWJlbCA9IHNnUk5BX3RhcmdldCkpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDEpICsgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IDApICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sID0gZ3JleSgwLjUpLCBsdHkgPSAyLCBzaXplID0gMC44KSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSA0LCBzbG9wZSA9IDEsIGNvbCA9IGdyZXkoMC41KSwgbHR5ID0gMiwgc2l6ZSA9IDAuOCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTQsIHNsb3BlID0gMSwgY29sID0gZ3JleSgwLjUpLCBsdHkgPSAyLCBzaXplID0gMC44KSArCiAgICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMsIG1heC5vdmVybGFwcyA9IDUwKSArCiAgICBsYWJzKHRpdGxlID0gdGl0bGUsIHggPSB2YXJzWzFdLCB5ID0gdmFyc1syXSkgKwogICAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC05LCA1KSwgeWxpbSA9IGMoLTksIDUpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhncmV5KDAuNSksIGN1c3RvbV9jb2xvcnNbMl0pKQp9CgojIGJyb3dzZSB0aHJvdWdoIGFsbCBwb3NzaWJsZSBjb25kaXRpb24gY29tYmluYXRpb25zOwojIHdlIG5lZWQgYSBoZWxwZXIgZnVuY3Rpb24gdGhhdCBkZXRlY3RzIGR1cGxpY2F0ZWQgY29tYmluYXRpb25zCmR1cGxpY2F0ZWRfMnZlYyA8LSBmdW5jdGlvbih4LCB5KSB7CiAgeHkgPSBwYXN0ZSh4LCB5KTsgeXggPSBwYXN0ZSh5LCB4KQogIHNhcHBseSh4eSwgZnVuY3Rpb24oeHZhbCkgewogICAgd2hpY2goeHZhbCA9PSB5eCkgPD0gd2hpY2goeHZhbCA9PSB4eSkKICB9KQp9CgpsaXN0X2NvbmRpdGlvbl9wYWlycyA8LSBsYXBwbHkoCiAgdW5pcXVlKGRmX2dlbmUkY29uZGl0aW9uKSAlPiUgZXhwYW5kX2dyaWQoeCA9IC4sIHkgPSAuKSAlPiUKICAgIGZpbHRlcighZHVwbGljYXRlZF8ydmVjKHgsIHkpKSAlPiUgdCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgYXMubGlzdCwKICBmdW5jdGlvbih2YXIpIHsKICAgIG1ha2VfZml0bmVzc19wbG90KGRmX2dlbmUsIHZhcnMgPSB2YXIsCiAgICAgIHRpdGxlID0gcGFzdGUodmFyLCBjb2xsYXBzZSA9ICIgIC0gICIpKQogIH0KKQoKIyBleHBvcnQgaW1hZ2VzCmludmlzaWJsZShjYXB0dXJlLm91dHB1dCgKICBsYXBwbHkobGlzdF9jb25kaXRpb25fcGFpcnMsIGZ1bmN0aW9uKHBsKSB7CiAgICBwbF9uYW1lIDwtIHBhc3RlMCgiLi4vZmlndXJlcy9wYWlyd2lzZV9jb21wYXJpc29ucy9wbG90XyIsIHBsJGxhYmVscyR4LCAiXyIsIHBsJGxhYmVscyR5LCAiLnBuZyIpCiAgICAjcG5nKGZpbGVuYW1lID0gcGxfbmFtZSwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDgwMCwgcmVzID0gMTIwKQogICAgI3ByaW50KHBsKQogICAgI2Rldi5vZmYoKQogIH0pCikpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNX0KIyBleGFtcGxlIG9mIGZpcnN0IDQgY29tYmluYXRpb25zCmxpc3RfY29uZGl0aW9uX3BhaXJzWzE6NF0KYGBgCgojIERpZmZlcmVudGlhbCBmaXRuZXNzIG9mIHNlbGVjdGVkIGdlbmUgc2V0cwoKIyMgQ2VudHJhbCBjYXJib24gbWV0YWJvbGlzbQoKVG8gcGxvdCBnZW5lIGZpdG5lc3MgZm9yIHRoZSBlbnp5bWVzIG9mIGNlbnRyYWwgY2FyYm9uIG1ldGFib2xpc20sIHdlIHVzZSB0aGUgY29tcGxldGUgbGlzdCBvZiBlbnp5bWVzIGFuZCB0aGUgZ2VuZXMgdGhhdCB0aGV5IGFyZSBtYXBwZWQgdG8gKG9idGFpbmVkIGZyb20gS0VHRykuIFdlIGNhbiBleHRyYWN0IGdlbmUgc2V0cyBmb3Igc3BlY2lmaWMgcGF0aHdheXMgYW5kIHBsb3QgZml0bmVzcy4gV2Ugc3RhcnQgd2l0aCBnbHljb2x5c2lzIGFuZCBDYWx2aW4gY3ljbGUgZW56eW1lcy4KCmBgYHtyfQpsaXN0X2NlbnRyYWxfbWV0X3BhdGh3YXlzIDwtIGMoCiAgIkdseWNvbHlzaXMgLyBHbHVjb25lb2dlbmVzaXMiLAogICJQZW50b3NlIHBob3NwaGF0ZSBwYXRod2F5IiwKICAiQ2FyYm9uIGZpeGF0aW9uIGluIHBob3Rvc3ludGhldGljIG9yZ2FuaXNtcyIsCiAgIlBob3Rvc3ludGhlc2lzIiwKICAiQ2l0cmF0ZSBjeWNsZSAoVENBIGN5Y2xlKSIsCiAgIlB5cnV2YXRlIG1ldGFib2xpc20iLAogICJHbHlveHlsYXRlIGFuZCBkaWNhcmJveHlsYXRlIG1ldGFib2xpc20iCikKYGBgCgoKYGBge3J9CnBsb3RfZ2VuZV9maXRuZXNzIDwtIGZ1bmN0aW9uKGRmLCBwdyA9IE5VTEwsIGdlbmUgPSBOVUxMLAogIHRpdGxlID0gTlVMTCwgbmNvbCA9IDgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSB7CiAgZGYgPC0gZGYgJT4lIGZpbHRlcih0aW1lID09IDApCiAgaWYgKCFpcy5udWxsKHB3KSkgewogICAgZGYgPC0gZGYgJT4lIGlubmVyX2pvaW4oZGZfa2VnZyAlPiUgZmlsdGVyKGtlZ2dfcGF0aHdheSA9PSBwdykgJT4lIHNlbGVjdChsb2N1cyksCiAgICAgIGJ5ID0gImxvY3VzIikKICAgIHRpdGxlIDwtIHB3CiAgfSBlbHNlIGlmICghaXMubnVsbChnZW5lKSkgewogICAgZGYgPC0gZGYgJT4lIGZpbHRlcihsb2N1cyAlaW4lIGdlbmUpCiAgfQogIAogIGdncGxvdChkZiwgYWVzKHggPSBjb25kaXRpb24sIHkgPSB3bWVhbl9maXRuZXNzLCAKICAgIHltaW4gPSB3bWVhbl9maXRuZXNzLXNkX2ZpdG5lc3MsIAogICAgeW1heCA9IHdtZWFuX2ZpdG5lc3Mrc2RfZml0bmVzcywKICAgIGZpbGwgPSBjb25kaXRpb24sCiAgICBjb2xvciA9IGNvbmRpdGlvbiwKICAgIGxhYmVsID0gaWZfZWxzZShwX2ZpdG5lc3NfYWRqIDw9IDAuMDEsICIqIiwgIiIpKSkgKwogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNikgKwogICAgZ2VvbV9lcnJvcmJhcihwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42LCBzaXplID0gMSkgKwogICAgZ2VvbV90ZXh0KHNpemUgPSA1LCBhZXMoeSA9IG1hcHBseShGVU4gPSBmdW5jdGlvbih4LCB5KSB7CiAgICAgIGlmICh4IDwgMCkgeCAtIHkgLSAyCiAgICAgIGVsc2UgeCArIHkgKyAwLjMKICAgIH0sIHdtZWFuX2ZpdG5lc3MsIHNkX2ZpdG5lc3MpKSkgKwogICAgY3VzdG9tX3RoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGxlZ2VuZC5wb3NpdGlvbiwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjQsICJjbSIpKSArIAogICAgbGFicyh0aXRsZSA9IHRpdGxlLCB4ID0gIiIsIHkgPSAiZml0bmVzcyIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzICA9IHNlcSgtMTAsIDEwLCA1KSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzWzE6NV0pKDExKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbG9yUmFtcFBhbGV0dGUoY3VzdG9tX2NvbG9yc1sxOjVdKSgxMSkpICsKICAgIGZhY2V0X3dyYXAofiBzZ1JOQV90YXJnZXQsIG5jb2wgPSBuY29sLCBkcm9wID0gRkFMU0UpCn0KYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2fQpwcmludChwbG90X2dlbmVfZml0bmVzcyhkZl9nZW5lLCBwdyA9IGxpc3RfY2VudHJhbF9tZXRfcGF0aHdheXNbWzFdXSkpCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1fQpwcmludChwbG90X2dlbmVfZml0bmVzcyhkZl9nZW5lLCBwdyA9IGxpc3RfY2VudHJhbF9tZXRfcGF0aHdheXNbWzJdXSkpCmBgYAoKYGBge3IsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1fQpwcmludChwbG90X2dlbmVfZml0bmVzcyhkZl9nZW5lLCBwdyA9IGxpc3RfY2VudHJhbF9tZXRfcGF0aHdheXNbWzNdXSkpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNH0KcHJpbnQocGxvdF9nZW5lX2ZpdG5lc3MoZGZfZ2VuZSwgcHcgPSBsaXN0X2NlbnRyYWxfbWV0X3BhdGh3YXlzW1s1XV0pKQpgYGAKCiMjIEdlbmUgZml0bmVzcyBpbiBtaXhvdHJvcGh5IGFuZCBoZXRlcm90cm9waHkKClVzaW5nIFtmbHVjdHVhdG9yXShodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL2ZsdWN0dWF0b3IpLCB3ZSBjYW4gaW1wb3J0IGEgY3VzdG9tIG1ldGFib2xpYyBtYXAgZm9yIF9TeW5lY2hvY3lzdGlzXyBzcC4gUENDIDY4MDMsIGFuZCBvdmVybGF5IHB1Ymxpc2hlZCBmbHV4ZXMgdGhhdCB3ZXJlIG1lYXN1cmVkIHdpdGggTEMtTVMgdXNpbmcgaXNvdG9waWNhbGx5IGxhYmVsbGVkIGNhcmJvbiBzb3VyY2VzIChbTmFrYWppbWEgZXQgYWwuLCAyMDE0XShodHRwczovL2RvaS5vcmcvMTAuMTA5My9wY3AvcGN1MDkxKSkuCgpGbHVjdHVhdG9yIGNhbiBiZSBpbnN0YWxsZWQgdXNpbmcgYSBmdW5jdGlvbiBmcm9tIGBkZXZ0b29sc2A6CgpgYGB7cn0KaWYgKCEiZmx1Y3R1YXRvciIgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkpIHsKICBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoIm0tamFobi9mbHVjdHVhdG9yIikKfQpgYGAKCldlIGltcG9ydCB0aGUgbWV0YWJvbGljIGZsdXggZGF0YSBmcm9tIHRoZSBzdXBwbGVtZW50YWwgaXRlbXMgb2YgW05ha2FqaW1hIGV0IGFsLiwgMjAxNF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvcGNwL3BjdTA5MSkuCgpgYGB7cn0KbGlicmFyeShmbHVjdHVhdG9yKQoKIyBpbXBvcnQgZmx1eCBkYXRhCmRmX25ha2FqaW1hX21mYSA8LSByZWFkLmNzdigiLi4vZGF0YS9pbnB1dC9OYWthamltYTIwMTRfbWV0YWJvbGljX2ZsdXhlcy5jc3YiKQoKIyBnZW5lcmF0ZSBzdHJva2Ugd2lkdGggYW5kIGNvbG9yCmRmX25ha2FqaW1hX21mYSA8LSBkZl9uYWthamltYV9tZmEgJT4lCiAgbXV0YXRlKAogICAgc3Ryb2tlX3dpZHRoID0gMC4zICsgKDAuNypzcXJ0KGFicyhmbHV4KSkpLAogICAgc3Ryb2tlX2NvbG9yID0gYWJzKGZsdXgpICU+JSB7MSsoLi9tYXgoLikpKjl9ICU+JSByb3VuZCwKICAgIHN0cm9rZV9jb2xvcl9yZ2IgPSAgY29sb3JSYW1wUGFsZXR0ZShjdXN0b21fY29sb3JzW2MoNSwyLDEpXSkoMTApW3N0cm9rZV9jb2xvcl0pCmBgYAoKVGhlIG5leHQgc3RlcCBpcyB0byBvdmVybGF5IHRoZSBmbHV4ZXMuIFdlIGdlbmVyYXRlIHR3byB0eXBlcyBvZiBtYXBzLCBtaXhvdHJvcGh5IGFuZCBwaG90b2hldGVyb3Ryb3BoeS4KVGhlIHN0cm9rZSB3aWR0aCBhbmQgY29sb3IgZm9yIGFsbCByZWFjdGlvbnMgaXMgc2V0IGJ5IHRoZSBmbHV4IG1hZ25pdHVkZS4KCmBgYHtyfQpmb3IgKGNvbmQgaW4gYygibWl4b3Ryb3BoIiwgInBob3RvaGV0ZXJvdHJvcGgiKSkgewogICMgaW1wb3J0IG1hcCAKICBTVkdfdGVtcGxhdGUgPC0gcmVhZF9zdmcoIi4uL2RhdGEvaW5wdXQvbWFwX2NlbnRyYWxfbWV0YWJvbGlzbV9zeW4uc3ZnIikKICAKICAjIHNldCBzdHJva2Ugb24gU1ZHIG1hcAogIFNWR19taXggPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHX3RlbXBsYXRlLAogICAgbm9kZSA9IGZpbHRlcihkZl9uYWthamltYV9tZmEsIGNvbmRpdGlvbiA9PSBjb25kKSRyZWFjdGlvbiwKICAgIGF0dHIgPSAic3R5bGUiLAogICAgcGF0dGVybiA9ICJzdHJva2Utd2lkdGg6WzAtOV0rXFwuWzAtOV0rIiwKICAgIHJlcGxhY2VtZW50ID0gcGFzdGUwKCJzdHJva2Utd2lkdGg6IiwKICAgICAgZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHN0cm9rZV93aWR0aCkpCiAgCiAgIyBzZXQgY29sb3IKICBTVkdfbWl4IDwtIHNldF9hdHRyaWJ1dGVzKFNWR19taXgsCiAgICBub2RlID0gZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHJlYWN0aW9uLAogICAgYXR0ciA9ICJzdHlsZSIsCiAgICBwYXR0ZXJuID0gInN0cm9rZTojYjNiM2IzIiwKICAgIHJlcGxhY2VtZW50ID0gcGFzdGUwKCJzdHJva2U6IiwKICAgICAgZmlsdGVyKGRmX25ha2FqaW1hX21mYSwgY29uZGl0aW9uID09IGNvbmQpJHN0cm9rZV9jb2xvcl9yZ2IpKQogIAogICMgc2V0IGFycm93IGRpcmVjdGlvbmFsaXR5CiAgU1ZHX21peCA8LSBzZXRfYXR0cmlidXRlcyhTVkdfbWl4LAogICAgbm9kZSA9IGZpbHRlcihkZl9uYWthamltYV9tZmEsIGNvbmRpdGlvbiA9PSBjb25kLCBmbHV4IDwgMCkkcmVhY3Rpb24sCiAgICBhdHRyID0gInN0eWxlIiwKICAgIHBhdHRlcm4gPSAibWFya2VyLWVuZDp1cmxcXCgjbWFya2VyWzAtOV0qXFwpOyIsCiAgICByZXBsYWNlbWVudCA9ICIiKQogIAogIFNWR19taXggPC0gc2V0X2F0dHJpYnV0ZXMoU1ZHX21peCwKICAgIG5vZGUgPSBmaWx0ZXIoZGZfbmFrYWppbWFfbWZhLCBjb25kaXRpb24gPT0gY29uZCwgZmx1eCA+IDApJHJlYWN0aW9uLAogICAgYXR0ciA9ICJzdHlsZSIsCiAgICBwYXR0ZXJuID0gIm1hcmtlci1zdGFydDp1cmxcXCgjbWFya2VyWzAtOV0qXFwpOyIsCiAgICByZXBsYWNlbWVudCA9ICIiKQogIAogIHdyaXRlX3N2ZyhTVkdfbWl4LCBmaWxlID0gcGFzdGUwKCIuLi9kYXRhL291dHB1dC9tYXBfIiwgY29uZCwgInkuc3ZnIikpCn0KYGBgCgpNZXRhYm9saWMgZmx1eCB3aXRoIG1peG90cm9waHkgfCAgTWV0YWJvbGljIGZsdXggd2l0aCBwaG90b2hldGVyb3Ryb3BoeQo6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06CiFbXSguLi9kYXRhL291dHB1dC9tYXBfbWl4b3Ryb3BoeS5zdmcpICB8ICAhW10oLi4vZGF0YS9vdXRwdXQvbWFwX3Bob3RvaGV0ZXJvdHJvcGh5LnN2ZykKCgpOb3cgd2UgcGxvdCBmaXRuZXNzIG9mIGNlbnRyYWwgY2FyYm9uIG1ldGFib2xpc20gZ2VuZXMgZm9yIHR3byBvciB0aHJlZSBzZWxlY3RlZCBjb25kaXRpb25zLiBUaGVzZSB3aWxsIGJlIGFkZGVkIHRvIHRoZSBtZXRhYm9saWMgbWFwIG1hbnVhbGx5LiBUaGUgbWl4b3Ryb3BoaWMgY29uZGl0aW9ucyBgTEMsIExMLCArR2AgYW5kIGBIQywgTEwsICtHYCB0dXJuZWQgb3V0IHRvIGJlIHZlcnkgc2ltaWxhci4KCmBgYHtyfQpkZl9jZW50cmFsY2FyYiA8LSB0aWJibGUoCiAgbG9jdXMgPSBjKAogICAgIyBFTVAKICAgICJzbGwwNTkzIiwgInNscjAzMjkiLCAic2xyMTM0OSIsICJzbHIwOTUyIiwgInNscjIwOTQiLAogICAgInNsbDAwMTgiLCAic2xyMDk0MyIsICJzbHIwNzgzIiwgInNscjA4ODQiLCAic2xsMTM0MiIsCiAgICAic2xyMDM5NCIsICJzbHIxOTQ1IiwgInNscjA3NTIiLCAic2xsMTI3NSIsICJzbGwwNTg3IiwKICAgICMgUFBQICsgQ0JCCiAgICAic2xyMTg0MyIsICJzbGwxNDc5IiwgInNsbDAzMjkiLCAic2xyMTc5MyIsICJzbGwxMDcwIiwKICAgICJzbGwwODA3IiwgInNscjAxOTQiLCAic3NsMjE1MyIsICJzbGwxNTI1IiwgInNscjAwMTIiLAogICAgInNscjAwMDkiLCAic3NsMzM2NCIsCiAgICAjIFB5cnV2YXRlIG1ldGFib2xpc20KICAgICJzbGwxODQxIiwgInNsbDE3MjEiLCAic2xyMTA5NiIsICJzbHIxOTM0IiwgInNsbDA0MDEiLAogICAgInNscjA3MjEiLCAic2xsMDkyMCIsCiAgICAjIFRDQQogICAgInNscjA2NjUiLCAic2xyMTI4OSIsICJzbHIxMDk2IiwgInNsbDEwMjMiLCAic2xsMTU1NyIsCiAgICAic2xyMTIzMyIsICJzbHIwMjAxIiwgInNsbDE2MjUiLCAic2xsMDgyMyIsICJzbHIwMDE4IiwKICAgICJzbGwwODkxIiksCiAgcmVhY3Rpb24gPSBjKAogICAgIyBFTVAKICAgICJIRVgiLCAiSEVYIiwgIlBHSSIsICJGQlAiLCAiRkJQIiwKICAgICJGQkEiLCAiRkJBIiwgIlRQSSIsICJHQVBESCIsICJHQVBESCIsCiAgICAiUEdLIiwgIlBHTSIsICJFTk8iLCAiUFlLIiwgIlBZSyIsCiAgICAjIFBQUCArIENCQgogICAgIkc2UERIIiwgIlBHTCIsICJHTkQiLCAiVEFMIiwgIlRLVCIsCiAgICAiUlBFIiwgIlJQSSIsICJSUEkiLCAiUFJVSyIsICJSVUJJU0NPIiwKICAgICJSVUJJU0NPIiwgIkNQMTIiLAogICAgIyBQeXJ1dmF0ZSBtZXRhYm9saXNtCiAgICAiUERIIiwgIlBESCIsICJQREgiLCAiUERIIiwgIkNTIiwgCiAgICAiTUUiLCAiUFBDIiwKICAgICMgVENBCiAgICAiQUNPTlQiLCAiSUNESCIsICJBS0dESCIsICJTVUNPQVMiLCAiU1VDT0FTIiwKICAgICJTVUNEIiwgIlNVQ0QiLCAiU1VDRCIsICJTVUNEIiwgIkZVTSIsCiAgICAiTURIIgogICAgKSkKCmRmX2NlbnRyYWxjYXJiIDwtIGRmX2dlbmUgJT4lIGZpbHRlcigKICAgIHRpbWUgPT0gMCwKICAgIGNvbmRpdGlvbiAlaW4lIGMoIkxDLCBMTCIsICJMQywgTEwsICtHIiwgIkxDLCBMTCwgK0QsICtHIikpICU+JQogIGlubmVyX2pvaW4oZGZfY2VudHJhbGNhcmIsIC4sIGJ5ID0gImxvY3VzIikgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHNnUk5BX3RhcmdldCAlPiUgZmN0X2lub3JkZXIpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSByZWNvZGUoY29uZGl0aW9uLCBgTEMsIExMYCA9ICJQaG90b3Ryb3BoeSIsIGBMQywgTEwsICtHYCA9ICJNaXhvdHJvcGh5IiwKICAgIGBMQywgTEwsICtELCArR2AgPSAiUGhvdG9oZXRlcm90cm9waHkiKSAlPiUgZmFjdG9yKC4sIHVuaXF1ZSguKVtjKDEsMywyKV0pKSAlPiUKICBtdXRhdGUocGF0aHdheSA9IHJlcChjKCJFTVAiLCAiUFBQICsgQ0JCIiwgIlB5cnV2YXRlIiwgIlRDQSIpLCBjKDQ1LDM2LDIxLDMzKSkpCmBgYAoKCgpgYGB7ciwgZmlnLndpZHRoID0gNC4wLCBmaWcuaGVpZ2h0ID0gN30KcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnIDwtIGRmX2NlbnRyYWxjYXJiICU+JQogIGdyb3VwX2J5KHBhdGh3YXkpICU+JSBncm91cF9zcGxpdCAlPiUKICBsYXBwbHkoZnVuY3Rpb24oZGYpIHsKICAgIGdncGxvdChkZiwgYWVzKHggPSBjb25kaXRpb24sIHkgPSB3bWVhbl9maXRuZXNzLCAKICAgICAgeW1pbiA9IHdtZWFuX2ZpdG5lc3Mtc2RfZml0bmVzcywKICAgICAgeW1heCA9IHdtZWFuX2ZpdG5lc3Mrc2RfZml0bmVzcywKICAgICAgZmlsbCA9IGNvbmRpdGlvbiwgY29sb3IgPSBjb25kaXRpb24sCiAgICAgIGxhYmVsID0gaWZfZWxzZShwX2ZpdG5lc3NfYWRqIDw9IDAuMDEsICIqIiwgIiIpKSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygwLCAtNSwgLTEwKSwgbGluZXR5cGUgPSAzLCBjb2wgPSBncmV5KDAuNikpICsKICAgIGdlb21fZXJyb3JiYXIocG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNiwgc2l6ZSA9IDEsIGFscGhhID0gMC43KSArCiAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42LCBmaWxsID0gIndoaXRlIiwgY29sb3IgPSAid2hpdGUiKSArCiAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42KSArCiAgICBnZW9tX3RleHQoc2l6ZSA9IDYsIGFlcyh5ID0gbWFwcGx5KEZVTiA9IGZ1bmN0aW9uKHgsIHkpIHsKICAgICAgaWYgKHggPCAwKSB4IC0geSAtIDMKICAgICAgZWxzZSB4ICsgeSArIDAuMwogICAgfSwgd21lYW5fZml0bmVzcywgc2RfZml0bmVzcykpKSArCiAgICBjdXN0b21fdGhlbWUoYXNwZWN0LnJhdGlvID0gMSwgbGVnZW5kLnBvc2l0aW9uID0gMCkgKyAKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGNvbG9yID0gIndoaXRlIiwgbWFyZ2luID0gbWFyZ2luKDEsIDEsIDEsIDEsICJwdCIpKSwKICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGxpbmV0eXBlID0gInNvbGlkIiwgY29sb3VyID0gZ3JleSgwLjYpLCBmaWxsID0gTkEsIHNpemUgPSAxLjApLAogICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBncmV5KDAuOTUpLCBjb2xvciA9IE5BKSwKICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gZ3JleSgwLjYpLCBjb2xvciA9IGdyZXkoMC42KSkKICAgICkgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiIikgKwogICAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC0xMSwgMSkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGFscGhhKGN1c3RvbV9jb2xvcnNbYygyLDMsMSldLCAwLjcpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoY3VzdG9tX2NvbG9yc1tjKDIsMywxKV0sIDAuNykpICsKICAgIGZhY2V0X3dyYXAofiBzZ1JOQV90YXJnZXQsIG5jb2wgPSA2KQogIH0pCgpnZ2FycmFuZ2UobnJvdyA9IDQsIGhlaWdodHMgPSAgYygwLjMzLCAwLjIyNCwgMC4yMjQsIDAuMjI0KSwKICBsYWJlbHMgPSBMRVRURVJTWzE6NF0sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbMV1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwtNSwwKSwgInBvaW50cyIpKSwKICBwbG90X2NlbnRyYWxjYXJiX21pbmlmaWdbWzJdXSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAgLDAsLTUsMCksICJwb2ludHMiKSksCiAgcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnW1szXV0gKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAwLC01LDApLCAicG9pbnRzIikpLAogIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbNF1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwtNSwwKSwgInBvaW50cyIpKQopCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKGZpbGVuYW1lID0gIi4uL2ZpZ3VyZXMvZmlndXJlMy5zdmciLCB3aWR0aCA9IDQuMCwgaGVpZ2h0ID0gNy4wKQpnZ2FycmFuZ2UobnJvdyA9IDQsIGhlaWdodHMgPSAgYygwLjMzLCAwLjIyNCwgMC4yMjQsIDAuMjI0KSwKICBsYWJlbHMgPSBMRVRURVJTWzE6NF0sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbMV1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwtNSwwKSwgInBvaW50cyIpKSwKICBwbG90X2NlbnRyYWxjYXJiX21pbmlmaWdbWzJdXSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAgLDAsLTUsMCksICJwb2ludHMiKSksCiAgcGxvdF9jZW50cmFsY2FyYl9taW5pZmlnW1szXV0gKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAwLC01LDApLCAicG9pbnRzIikpLAogIHBsb3RfY2VudHJhbGNhcmJfbWluaWZpZ1tbNF1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMCwtNSwwKSwgInBvaW50cyIpKQopCmRldi5vZmYoKQpgYGAKCgojIyBBZGFwdGF0aW9uIHRvIGxpZ2h0IGFuZCBjYXJib24gZXhjZXNzCgpXZSB3aWxsIGxvb2sgYXQgZGlmZmVyZW50IHR5cGVzIG9mIHJlZ3VsYXRvcnkgYWRhcHRhdGlvbnM6CgotIHBob3Rvc3lzdGVtIHN1YnVuaXRzLCBwYXJ0aWN1bGFybHkgUFNJSQotIGBhcGNgL2BjcGNgYW50ZW5uYSBwcm90ZWlucyAocGh5Y29iaWxpc29tZXMpLCBrbm93biB0byBiZSBhbW9uZyB0aGUgbW9zdCBleHByZXNzZWQgYW5kIHJlZ3VsYXRlZCBnZW5lcyBpbiBjeWFub3MKLSBmbGF2b3Byb3RlaW5zIEZsdjEgKGBzbGwxNTIxYCksIEZsdjIgKGBzbGwwMjE5YCksIEZsdjMgKGBzbGwwNTUwYCksIEZsdjQgKGBzbGwwMjE3YCksIGBzbGwwMjE4YCAoaW4gZmx2Mi80IG9wZXJvbikKLSBsb3cgYWZmaW5pdHkvaGlnaCBmbHV4IHRyYW5zcG9ydGVycyBDaSB0cmFuc3BvcnRlcnM6IGJpY0EgKGBzbGwwODM0YCksIE5ESC1JNCB3aXRoIG5kaEY0LCBENCwgY3VwQiAoYHNsbDAwMjZgLCBgc2xsMDAyN2AsIGBzbHIxMzAyYCkKLSBoaWdoIGFmZmluaXR5L2xvdyBmbHV4IGluZHVjaWJsZSBDaSB0cmFuc3BvcnRlcnM6IEJDVDEvY21wQUIocG9yQilDRCAoYHNscjAwNDAtNDRgKSwgU2J0QS9CIChgc2xyMTUxMmAsIGBzbHIxNTEzYCksIE5ESC1JMyB3aXRoIG5kaEYzLCBuZGhEMywgY3VwQSwgY3VwUyAoYHNsbDE3MzItMzVgKQotIGNhcmJvbiB0cmFuc3BvcnQgcmVndWxhdG9yeSBwcm90ZWluczogY2NtUi9yYmNSIChgc2xsMTU5NGApLCBjbXBSIChgc2xsMDAzMGApLCBjeWFickIxIChgc2xsMDM1OWApLCBjeWFickIyIChgc2xsMDgyMmApCgoKYGBge3J9CnBsb3RfcGhvdG9zeXN0ZW0yIDwtIGRmX2dlbmUgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHN0cl9yZXBsYWNlKHNnUk5BX3RhcmdldCwgInBzYjEzIiwgInBzYlciKSkgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qoc2dSTkFfdGFyZ2V0LCAicHNiW0JDREVGSElKS0xPVVZYWl0kIiksIHRpbWUgPT0gMCkgJT4lCiAgcGxvdF9nZW5lX2ZpdG5lc3MobmNvbCA9IDYsIGxlZ2VuZC5wb3NpdGlvbiA9IDApCgpwbG90X3BoeWNvYmlsaXNvbWUgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoZ2VuZV9uYW1lLCAiW2FjXXBjW0FCQ0RFRkddIikpICU+JQogIHBsb3RfZ2VuZV9maXRuZXNzKG5jb2wgPSA2LCBsZWdlbmQucG9zaXRpb24gPSAwKQoKcGxvdF9mbHZfZ2VuZXMgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKGxvY3VzICVpbiUgYygic2xsMTUyMSIsICJzbGwwMjE5IiwgInNsbDA1NTAiLCAic2xsMDIxNyIsICJzbGwwMjE4IikpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSByZWNvZGUoc2dSTkFfdGFyZ2V0LCBgc2xsMTUyMWAgPSAiRmx2MSAoc2xsMTUyMSkiLCBgc2xsMDIxOWAgPSAiRmx2MiAoc2xsMDIxOSkiLAogICAgYHNsbDA1NTBgID0gIkZsdjMgKHNsbDA1NTApIiwgYHNsbDAyMTdgID0gIkZsdjQgKHNsbDAyMTcpIikpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBmYWN0b3Ioc2dSTkFfdGFyZ2V0LCBjKHVuaXF1ZShzZ1JOQV90YXJnZXQpLCAiIikpKSAlPiUKICBwbG90X2dlbmVfZml0bmVzcyhuY29sID0gNiwgbGVnZW5kLnBvc2l0aW9uID0gMCkKCnBsb3RfY2FyYm9uX3VwdGFrZSA8LSBkZl9nZW5lICU+JSBmaWx0ZXIobG9jdXMgJWluJSBjKAogICAgInNsbDAwMjYiLCAic2xsMDAyNyIsICJzbHIxMzAyIiwgInNsbDE3MzIiLAogICAgInNsbDE3MzMiLCAic2xsMTczNCIsICJzbGwxNzM1IiwgInNscjAwNDAiLAogICAgInNscjAwNDEiLCAic2xyMDA0MyIsICJzbHIwMDQ0IgogICkpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSByZWNvZGUoc2dSTkFfdGFyZ2V0LAogICAgYG5ydEMyYCA9ICJjbXBDIiwgYG5ydEQzYCA9ICJjbXBEIiwKICAgIGBzbGwxNzM0YCA9ICJjdXBBIiwgYHNscjEzMDJgID0gImN1cEIiLAogICAgYHNsbDE3MzVgID0gImN1cFMiLCBgbmRoRjJgID0gIm5kaEYzIgogICkpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBmYWN0b3Ioc2dSTkFfdGFyZ2V0LCB1bmlxdWUoc2dSTkFfdGFyZ2V0KVtjKDQsNiwxMSwzLDUsOSwxMCwxLDIsNyw4KV0pKSAlPiUKICBwbG90X2dlbmVfZml0bmVzcyhuY29sID0gNiwgbGVnZW5kLnBvc2l0aW9uID0gMCkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtNy45LCAyLjQpKQpgYGAKCkZpZ3VyZSAyIGRyYWZ0OgoKYGBge3IsIGZpZy53aWR0aCA9IDYuMCwgZmlnLmhlaWdodCA9IDEwLjV9CmdnYXJyYW5nZShucm93ID0gNCwgaGVpZ2h0cyA9ICBjKDAuMzIsIDAuMzIsIDAuMTMsIDAuMjMpLAogIGxhYmVscyA9IExFVFRFUlNbMTo0XSwKICBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X3Bob3Rvc3lzdGVtMiArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAsIDEyLCAwLCAxMiksInBvaW50cyIpKSwKICBwbG90X3BoeWNvYmlsaXNvbWUgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAxMiwgMCwgMTIpLCJwb2ludHMiKSksCiAgcGxvdF9mbHZfZ2VuZXMgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYyg0LjcsIDEyLCA0LjcsIDEyKSwicG9pbnRzIikpLAogIHBsb3RfY2FyYm9uX3VwdGFrZSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDQuMywgMTIsIDQuMywgMTIpLCJwb2ludHMiKSkKKQpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CnN2ZyhmaWxlbmFtZSA9ICIuLi9maWd1cmVzL2ZpZ3VyZTIuc3ZnIiwgd2lkdGggPSA2LjAsIGhlaWdodCA9IDEwLjUpCmdnYXJyYW5nZShucm93ID0gNCwgaGVpZ2h0cyA9ICBjKDAuMzIsIDAuMzIsIDAuMTMsIDAuMjMpLAogIGxhYmVscyA9IExFVFRFUlNbMTo0XSwKICBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICBwbG90X3Bob3Rvc3lzdGVtMiArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAsIDEyLCAwLCAxMiksInBvaW50cyIpKSwKICBwbG90X3BoeWNvYmlsaXNvbWUgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAxMiwgMCwgMTIpLCJwb2ludHMiKSksCiAgcGxvdF9mbHZfZ2VuZXMgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYyg0LjcsIDEyLCA0LjcsIDEyKSwicG9pbnRzIikpLAogIHBsb3RfY2FyYm9uX3VwdGFrZSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDQuMywgMTIsIDQuMywgMTIpLCJwb2ludHMiKSkKKQpkZXYub2ZmKCkKYGBgCgpBcyBhIFN1cHBsZW1lbnRhcnkgZmlndXJlIHRvIEMpLCB3ZSBjYW4gKipwbG90IGFsbCBvdGhlciBjYXJib24gdHJhbnNwb3J0ZXJzIGFuZCByZWd1bGF0b3J5IGdlbmVzKiogdGhhdCBzaG93ZWQgYSBsZXNzIHJlbWFya2FibGUgZWZmZWN0LgoKYGBge3IsIGZpZy53aWR0aCA9IDYsICBmaWcuaGVpZ2h0ID0gMi43NX0KcGxvdF9jYXJib25fdXB0YWtlXzIgPC0gZGZfZ2VuZSAlPiUgZmlsdGVyKGxvY3VzICVpbiUgYygKICAgICJzbGwwODM0IiwgInNscjE1MTIiLCAic2xyMTUxMyIsICJzbGwxNTk0IiwgInNsbDAwMzAiLCAic2xsMDM1OSIsICJzbGwwODIyIgogICkpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSByZWNvZGUoc2dSTkFfdGFyZ2V0LAogICAgYHNsbDA4MzRgID0gImJpY0EiLCBgc2xyMTUxMmAgPSAic2J0QSIsIGBzbHIxNTEzYCA9ICJzYnRCIiwKICAgIGBzbGwwMzU5YCA9ICJjeWFickIxIiwgYHNsbDA4MjJgID0gImN5YWJyQjIiLCBgcmJjUmAgPSAiY2NtUiIKICApKSAlPiUKICBtdXRhdGUoc2dSTkFfdGFyZ2V0ID0gZmFjdG9yKHNnUk5BX3RhcmdldCwgdW5pcXVlKHNnUk5BX3RhcmdldCkpKSAlPiUKICBwbG90X2dlbmVfZml0bmVzcyhuY29sID0gNCwgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikKCnBsb3RfY2FyYm9uX3VwdGFrZV8yCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0Kc3ZnKGZpbGVuYW1lID0gIi4uL2ZpZ3VyZXMvZmlndXJlUzIuc3ZnIiwgd2lkdGggPSA2LjAsIGhlaWdodCA9IDIuNzUpCnByaW50KHBsb3RfY2FyYm9uX3VwdGFrZV8yKQpkZXYub2ZmKCkKYGBgCgoKQXMgYW5vdGhlciBTdXBwbGVtZW50YXJ5IEZpZ3VyZSwgd2UgY2FuIHBsb3QgdGhlICoqdG90YWwgcHJvdGVpbiBtYXNzIG9mIHRoZSBwaHljb2JpbGlzb21lKiogZGV0ZXJtaW5lZCBieSBwcm90ZWluIG1hc3Mgc3BlY3Ryb21ldHJ5LgpUaGlzIGRhdGEgd2FzIHB1Ymxpc2hlZCBpbiBvdXIgc3R1ZHkgW0phaG4gZXQgYWwuLCBDZWxsIFJlcG9ydHMsIDIwMThdKGh0dHBzOi8vd3d3LmNlbGwuY29tL2NlbGwtcmVwb3J0cy9mdWxsdGV4dC9TMjIxMS0xMjQ3KDE4KTMxNDg1LTIpLiBUaGUgZGF0YSBjYW4gYmUgZG93bmxvYWRlZCBkaXJlY3RseSBmcm9tIHRoZSBTaGlueVByb3QgZ2l0aHViIHBhZ2Ugd2hlcmUgaXQgaXMgaW5jbHVkZWQgZm9yIG9uIGRlbWFuZCB2aXN1YWxpemF0aW9uLgoKYGBge3IsIGZpZy53aWR0aCA9IDYsIGZpZy5oZWlnaHQgPSAzLjZ9CmxvYWQodXJsKCJodHRwczovL2dpdGh1Yi5jb20vbS1qYWhuL1NoaW55UHJvdC9ibG9iL21hc3Rlci9kYXRhL0phaG5fMjAxOF9MaWdodF9hbmRfQ08yX2xpbS5SZGF0YT9yYXc9dHJ1ZSIpKQoKcGxvdF9wcm90bWFzc19waHljb2JpbGlzb21lMSA8LSBKYWhuXzIwMThfTGlnaHRfYW5kX0NPMl9saW0gJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QocHJvdGVpbiwgIlthY11wY1tBQkNERUZHXSIpLCBzYW1wbGUgIT0gIkNPMiIpICU+JQogIG11dGF0ZShwcm90ZWluID0gc3RyX2V4dHJhY3QocHJvdGVpbiwgIlthY11wY1tBQkNERUZHXVsxMl0/IikpICU+JQogIGdncGxvdChhZXMoeCA9IGZhY3RvcihsaWdodCksIHkgPSAxMDAqbWVhbl9tYXNzX2ZyYWN0aW9uX25vcm0sIAogIGZpbGwgPSBzdHJfc3ViKHByb3RlaW4sIDEsIDMpLAogIGxhYmVsID0gaWZfZWxzZShzdHJfZGV0ZWN0KHByb3RlaW4sICJbYWNdcGNbQy1aXVsxMl0/IiksICIiLCBwcm90ZWluKSkpICsKICBsaW1zKHkgPSBjKDAsIDIyKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gInN0YWNrIiwgd2lkdGggPSAwLjcsIGNvbCA9IGdyZXkoMSksIHNpemUgPSAwLjIpICsKICBnZW9tX3RleHQoc2l6ZSA9IDIuNSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGNvbG9yID0gIndoaXRlIikgKwogIGN1c3RvbV90aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjUsICJjbSIpKSArCiAgbGFicyh0aXRsZSA9ICJMaWdodCBsaW1pdGF0aW9uIiwgeCA9IGV4cHJlc3Npb24oIsK1bW9sIHBob3RvbnMgbSJeLTIqIiBzIl4tMSksIHkgPSAiJSBwcm90ZWluIG1hc3MiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzhlYjY1NSIsICIjOTM3ZmIzIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzhlYjY1NSIsICIjOTM3ZmIzIikpCgpwbG90X3Byb3RtYXNzX3BoeWNvYmlsaXNvbWUyIDwtIEphaG5fMjAxOF9MaWdodF9hbmRfQ08yX2xpbSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChwcm90ZWluLCAiW2FjXXBjW0FCQ0RFRkddIiksIHNhbXBsZSA9PSAiQ08yIikgJT4lCiAgbXV0YXRlKHByb3RlaW4gPSBzdHJfZXh0cmFjdChwcm90ZWluLCAiW2FjXXBjW0FCQ0RFRkddWzEyXT8iKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKGNvMl9jb25jZW50cmF0aW9uKSwgeSA9IDEwMCptZWFuX21hc3NfZnJhY3Rpb25fbm9ybSwgCiAgZmlsbCA9IHN0cl9zdWIocHJvdGVpbiwgMSwgMyksCiAgbGFiZWwgPSBpZl9lbHNlKHN0cl9kZXRlY3QocHJvdGVpbiwgIlthY11wY1tDLVpdWzEyXT8iKSwgIiIsIHByb3RlaW4pKSkgKwogIGxpbXMoeSA9IGMoMCwgMjIpKSArCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAic3RhY2siLCB3aWR0aCA9IDAuNywgY29sID0gZ3JleSgxKSwgc2l6ZSA9IDAuMikgKwogIGdlb21fdGV4dChzaXplID0gMi41LCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwgY29sb3IgPSAid2hpdGUiKSArCiAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuNSwgImNtIikpICsKICBsYWJzKHRpdGxlID0gIkNPMiBsaW1pdGF0aW9uIiwgeCA9ICIlIENPMiBpbiBhaXIiLCB5ID0gIiUgcHJvdGVpbiBtYXNzIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiM4ZWI2NTUiLCAiIzkzN2ZiMyIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiM4ZWI2NTUiLCAiIzkzN2ZiMyIpKQoKZ2dhcnJhbmdlKG5jb2wgPSAyLCB3aWR0aHMgPSBjKDAuNSwwLjUpLAogIGxhYmVscyA9IExFVFRFUlNbMToyXSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgcGxvdF9wcm90bWFzc19waHljb2JpbGlzb21lMSwKICBwbG90X3Byb3RtYXNzX3BoeWNvYmlsaXNvbWUyCikKYGBgCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpzdmcoIi4uL2ZpZ3VyZXMvZmlndXJlUzEuc3ZnIiwgd2lkdGggPSA2LCBoZWlnaHQgPSAzLjYpCmdnYXJyYW5nZShuY29sID0gMiwgd2lkdGhzID0gYygwLjUsMC41KSwKICBsYWJlbHMgPSBMRVRURVJTWzE6Ml0sIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfcHJvdG1hc3NfcGh5Y29iaWxpc29tZTEsCiAgcGxvdF9wcm90bWFzc19waHljb2JpbGlzb21lMgopCmRldi5vZmYoKQpgYGAKCk90aGVyIGdlbmVzIG9mIGludGVyZXN0IHRoYXQgZWl0aGVyIGRpZCBub3Qgc2hvdyBhbnkgKHJlbWFya2FibGUpIGVmZmVjdCBvbiBmaXRuZXNzLCBvciBkbyBub3QgbWVldCB0aGUgc2NvcGUgb2YgdGhpcyBzZWN0aW9uOgoKLSBPQ1AgKGBzbHIxOTYzYCksIHBncjUgKGBzc3IyMDE2YCkKLSBTaWdCIChgc2xsMDMwNmApLCBTaWdDIChgc2xsMDE4NGApLCBTaWdEIChgc2xsMjAxMmApLCBTaWdFIChgc2xsMTY4OWApIChycG9EIGdlbmVzIDEtNCkKLSBjY21NIChgc2xsMTAzMWApLCBjY21LMiAoYHNsbDEwMjhgKSwgY2NtSzEgKGBzbGwxMDI5YCksIGNjbU4gKGBzbGwxMDMyYCksIGNjbU8gKGBzbHIwNDM2YCksCiAgY2NtTCAoYHNsbDEwMzBgKQoKQWx0ZXJuYXRpdmUgcmVwcmVzZW50YXRpb24gb2YgcGhvdG9zeXN0ZW1zIGdlbmUgZml0bmVzcyBhcyBoZWF0IG1hcHM6CgpgYGB7ciwgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDR9CnBsb3Rfc2dSTkFzX3BzMSA8LSBkZl9nZW5lICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHNnUk5BX3RhcmdldCwgInBzYVtBLVpdKiIpLCB0aW1lID09IDApICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIGdncGxvdChhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IGZjdF9yZXYoc2dSTkFfdGFyZ2V0KSwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUoKSArCiAgZ2VvbV90ZXh0KHNpemUgPSA0LCBudWRnZV95ID0gLTAuNCwgY29sb3IgPSBncmV5KDAuNCksCiAgICBhZXMobGFiZWwgPSBpZl9lbHNlKHBfZml0bmVzc19hZGogPD0gMC4wMSwgIioiLCAiIikpKSArCiAgbGFicyh0aXRsZSA9ICJQaG90b3N5c3RlbSBJIiwgeCA9ICIiLCB5ID0gIiIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKGN1c3RvbV9jb2xvcnNbMV0sIGdyZXkoMC45KSwgY3VzdG9tX2NvbG9yc1syXSksCiAgICBsaW1pdHMgPSBjKC00LCA0KSkKCnBsb3Rfc2dSTkFzX3BzMiA8LSBkZl9nZW5lICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KHNnUk5BX3RhcmdldCwgInBzYltBLVoxXVsyM10/JCIpLCB0aW1lID09IDApICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBzdHJfcmVwbGFjZShzZ1JOQV90YXJnZXQsICJwc2IxMyIsICJwc2JXIikpICU+JQogIGdncGxvdChhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IGZjdF9yZXYoc2dSTkFfdGFyZ2V0KSwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKyBjdXN0b21fdGhlbWUoKSArCiAgZ2VvbV90ZXh0KHNpemUgPSA0LCBudWRnZV95ID0gLTAuNCwgY29sb3IgPSBncmV5KDAuNCksCiAgICBhZXMobGFiZWwgPSBpZl9lbHNlKHBfZml0bmVzc19hZGogPD0gMC4wMSwgIioiLCAiIikpKSArCiAgbGFicyh0aXRsZSA9ICJQaG90b3N5c3RlbSBJSSIsIHggPSAiIiwgeSA9ICIiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSkpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gYyhjdXN0b21fY29sb3JzWzFdLCBncmV5KDAuOSksIGN1c3RvbV9jb2xvcnNbMl0pLAogICAgbGltaXRzID0gYygtNCwgNCkpCgpnZ2FycmFuZ2UobmNvbCA9IDIsIHBsb3Rfc2dSTkFzX3BzMSwgcGxvdF9zZ1JOQXNfcHMyKQpgYGAKCi0gVGhlIHBzYksga25vY2tkb3duIGhhcyBhIHN0cm9uZyBmaXRuZXNzIHBlbmFsdHkgaW4gYWxsIGNvbmRpdGlvbnMsIHN1Z2dlc3RpbmcgYSB1bml2ZXJzYWwgcm9sZQotIGhvd2V2ZXIgaXQgaXMgbm90IHZlcnkgc2lnbmlmaWNhbnQgaW4gV2lsY294IHJhbmsgc3VtIHRlc3QKLSB0aGlzIGlzIGR1ZSB0byBvbmx5IGhhdmluZyAzIHNnUk5BcywgdGhleSBhY3R1YWxseSBhbGwgc2hvdyB0aGUgc2FtZSBwaGVub3R5cGUKCmBgYHtyLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gNX0KZGZfbWFpbiAlPiUKICBmaWx0ZXIoc2dSTkFfdGFyZ2V0ICVpbiUgYygicHNiSyIpKSAlPiUKICBtdXRhdGUobG9jdXMgPSBmYWN0b3IobG9jdXMpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSB0aW1lLCB5ID0gbG9nMkZvbGRDaGFuZ2UsIGNvbG9yID0gZmFjdG9yKHNnUk5BX2luZGV4KSkpICsKICBnZW9tX2xpbmUoc2l6ZSA9IDEpICsKICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY3VzdG9tX3JhbmdlKDUpKSArCiAgZmFjZXRfd3JhcCggfiBjb25kaXRpb24pCmBgYAoKCiMjIEdlbmVzIHdoZXJlIGtub2NrIGRvd24gbGVhZHMgdG8gaW5jcmVhc2VkIGZpdG5lc3MKCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9Cmxpc3RfZ2VuZXNfcG9zX2ZpdG5lc3MgPC0gZGZfZ2VuZSAlPiUKICBmaWx0ZXIodGltZSA9PSAwLCAhaXMubmEobG9jdXMpLCB3bWVhbl9maXRuZXNzID4gMikgJT4lCiAgcHVsbChsb2N1cykgJT4lIHVuaXF1ZQoKcGxvdF9nZW5lX2ZpdG5lc3MoZGZfZ2VuZSwgZ2VuZSA9IGxpc3RfZ2VuZXNfcG9zX2ZpdG5lc3MsIHRpdGxlID0gIkdlbmVzIHdpdGggaW5jcmVhc2VkIGZpdG5lc3MgKGYgPiAyKSIpCmBgYAoKU3VtbWFyeToKLSBwbWdBIGlzIG9uY2UgYWdhaW4gdGhlIGdlbmUgd2l0aCBzdHJvbmdlc3QgYW5kIG1vc3Qgd2lkZXNwcmVhZCBmaXRuZXNzIGluY3JlYXNlLCB2YWxpZGF0aW5nIHJlc3VsdHMgZnJvbSBsaWJyYXJ5IFYxCi0gc2xyMTkxNiBzYW1lIHBoZW5vdHlwZSBhcyBwbWdBIGp1c3Qgd2Vha2VyLiBXZSBhbHNvIGtub3cgdGhpcyBvbmUgZnJvbSBiZWZvcmUuIE11c3QgaGF2ZSBpZGVudGljYWwgcm9sZSBhcyBwbWdBLgotIGFsbCBQU0lJIGdlbmVzIHNob3cgaW5jcmVhc2VkIGZpdG5lc3MgaW4gcGhvdG9oZXRlcm90cm9waGljIGNvbmRpdGlvbiAtLT4gUFMgaXMgYSBidXJkZW4gaGVyZQotIHNsbDA2ODksIHB4Y0EsIHNscjE2MDkgLSBhbGwgaW5jcmVhc2VkIGZpdG5lc3MgaW4gSEMsSEwsIGZpcnN0IHR3byBhcmUgTmErL0NPMiAoPykgdHJuYXNwb3J0ZXJzLAogIHNscjE2MDkgd2Uga25vdyBmcm9tIGJlZm9yZSwgYW5ub3RhdGVkIGFzIGZhdHR5IGFjaWQgQ29BIGxpZ2FzZSwgYnV0IHByb2JhYmx5IGl0J3Mgc29tZXRoaW5nIGRpZmZlcmVudAotIHNsbDYwNTUsIHNscjE1MDUsIHNscjE5OTAgLSBhbGwgaW5jcmVhc2VkIGZpdG5lc3MgaW4gcGhvdG9oZXRlcm90cm9waGljIGNvbmRpdGlvbiwgYW5kIGRlY3JlYXNlZCBmaXRuZXNzIGluIEhDL0xMIGNvbmRpdGlvbnMuCiAgTm90IG11Y2ggaXMga25vd24gYWJvdXQgdGhlc2UgZ2VuZXMsIHByb2JhYmx5IGEgcm9sZSBpbiBwaG90b3N5bnRoZXNpcywgYXMgdGhlIHBhdHRlcm4gaXMgc2ltaWxhciB0byBwc2IgZ2VuZXMgKFBTSUkgbWF0dXJhdGlvbj8pCi0gc2xyMDgxMywgc2xyMDkwNywgc2xyOTA5LCBzbHIxMjk5IC0gYWxsIGluY3JlYXNlZCBmaXRuZXNzIGluIEhDL0xMLiBOb3QgY2xlYXIgd2hhdCBjb25uZWN0cyB0aGVzZSBnZW5lcyBmdW5jdGlvbmFsbHkuCgoKIyBEaWZmZXJlbnRpYWwgZml0bmVzcyBvZiBub24tY29kaW5nIFJOQXMgKG5jUk5BcykKCiMjIEdlbmVyYWwgdHJlbmRzCgpUaGUgZmlyc3QgdGFzayB0byBzdHVkeSBuY1JOQXMgaXMgdG8gZ2VuZXJhdGUgYSBuZXcgZGF0YSBmcmFtZSB3aXRoIGFkZGl0aW9uYWwgYW5ub3RhdGlvbiBmb3IgbmNSTkFzLgpBZGRpdGlvbmFsIGFubm90YXRpb24gdGFibGVzIHdlcmUgZXhwb3J0ZWQgZnJvbSBHZW5laW91cyBhbmQgYXJlIGJhc2VkIG9uIHRoZSBwdWJsaWNhdGlvbiBmcm9tIFtNaXRzY2hrZSBldCBhbC4sIFBOQVMsIDIwMTBdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDczL3BuYXMuMTAxNTE1NDEwOCkuIEFjY29yZGluZyB0byB0aGlzIHB1YmxpY2F0aW9uLCBuY1JOQXMgYXJlIGdyb3VwZWQgaW50byBmb3VyIGRpZmZlcmVudCAoc2xpZ2h0bHkgb3ZlcmxhcHBpbmcpIGNsYXNzZXM6CgotIG5vbi1jb2RpbmcgcmVndWxhdG9yeSBSTkFzIChuY1JOQXMgaW4gc3RyaWN0IHNlbnNlKSBub3QgYXNzb2NpYXRlZCB0byBhIGdlbmUKLSBpVFNTLCBpbnRlcm5hbCBUU1Mgd2l0aGluIGEgZ2VuZQotIGFzUk5BLCByZWd1bGF0b3J5IGFudGktc2Vuc2UgUk5BcyBhc3NvY2lhdGVkIHdpdGggYSBnZW5lCi0gTm90IGluY2x1ZGVkOiA1J1VUUnMsIGFsdGVybmF0aXZlIHRyYW5zY3JpcHRpb24gc3RhcnQgc2l0ZXMgKFRTUykgYXNzb2NpYXRlZCB0byBhIGdlbmUKCmBgYHtyfQpkZl9uY1JOQSA8LSBkZl9tYWluICU+JSBmaWx0ZXIoc2dSTkFfdHlwZSA9PSAibmNSTkEiKSAlPiUKICAjIG9idGFpbiBudW1iZXIgb2Ygc2dSTkFzIHBlciB0YXJnZXQKICBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIHN1bW1hcml6ZShzZ1JOQV9udW1iZXIgPSBsZW5ndGgodW5pcXVlKHNnUk5BX3Bvc2l0aW9uKSkpICU+JQogICMgbWVyZ2Ugd2l0aCBkZl9nZW5lIHRhYmxlCiAgaW5uZXJfam9pbihkZl9nZW5lLCBieSA9ICJzZ1JOQV90YXJnZXQiKSAlPiUKICAjIGdlbmVyYXRlIG5jUk5BIHR5cGUgYmFzZWQgb24gdGFyZ2V0IG5hbWUKICBzZWxlY3QoLWxvY3VzLCAtZ2VuZV9uYW1lLCAtc2dSTkFfdHlwZSkgJT4lCiAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IHN0cl9zdWIoc2dSTkFfdGFyZ2V0LCA0LCAxMDAwKSkgJT4lCiAgbGVmdF9qb2luKGJ5ID0gInNnUk5BX3RhcmdldCIsCiAgICBiaW5kX3Jvd3MobGFwcGx5KGMoIk5DXzAwMDkxMV9uY1JOQS50c3YiLCAiTkNfMDAwOTExX2FzUk5BLnRzdiIsICJOQ18wMDA5MTFfaVRTUy50c3YiKSwKICAgICAgRlVOID0gZnVuY3Rpb24oZikgcmVhZF90c3YocGFzdGUwKCIuLi9kYXRhL2lucHV0LyIsIGYpLCBjb2xfdHlwZXMgPSBjb2xzKCkpCiAgICApKQogICkKCmRmX25jUk5BICU+JSBmaWx0ZXIodGltZSA9PSAwKSAlPiUKICBncm91cF9ieShuY1JOQV90eXBlLCBzZ1JOQV90YXJnZXQpICU+JQogIHN1bW1hcml6ZShjb21iX3Njb3JlID0gYW55KGNvbWJfc2NvcmUgPj0gNCksIC5ncm91cHMgPSAiZHJvcF9sYXN0IikgJT4lCiAgc3VtbWFyaXplKC5ncm91cHMgPSAiZHJvcCIsCiAgICBuX3RhcmdldHMgPSBsZW5ndGgodW5pcXVlKHNnUk5BX3RhcmdldCkpLAogICAgYHNpZ25pZmljYW50IChzY29yZSA+PSA0KWAgPSBzdW0oY29tYl9zY29yZSksCiAgICBgbm9uIHNpZ24uIChzY29yZSA8IDQpYCA9IHN1bSghY29tYl9zY29yZSksCiAgICBgJSBzaWduLmAgPSBgc2lnbmlmaWNhbnQgKHNjb3JlID49IDQpYC9uX3RhcmdldHMqMTAwCiAgKQpgYGAKCkxvb2tpbmcgYXQgdGhlIGZpdG5lc3MgYW5kIHNpZ25pZmljYW5jZSBzY29yZXMgZm9yIG9uZSBjb25kaXRpb25zLCBpdCBzZWVtcyBhcyBpbnRlcm5hbCB0cmFuc2NyaXB0aW9uIHN0YXJ0IHNpdGVzIGFyZSBvdmVycmVwcmVzZW50ZWQgaW4gdGhlIGdyb3VwIHRoYXQgc2hvd3MgYW4gZWZmZWN0LiBUaGlzIGlzIG5vdCBhIHN1cnByaXNlLCBnaXZlbiB0aGF0IHNnUk5BcyB0YXJnZXRpbmcgaVRTUyBiYXNpY2FsbHkgYWxzbyByZXByZXNzIHRoZSBuYXRpdmUgZ2VuZSBhcyBhIHJlZ3VsYXIgc2dSTkEuIFRoZSBsaWJyYXJ5IGNvbnRhaW5zIDE3MTIgbmNSTkFzIGVhY2ggdGFyZ2V0ZWQgYnkgMSB0byA1IHNnUk5Bcy4gT25seSB2ZXJ5IGZldyBvZiB0aG9zZSBzaG93ZWQgYW4gZWZmZWN0IG9uIGZpdG5lc3MuCldlIGNhbiBmaWx0ZXIgYWxsIG5jUk5BcyB0aGF0IGhhdmUgYSAic2lnbmlmaWNhbmNlIiBlcXVpdmFsZW50IHRvIGEgZml0bmVzcyBzY29yZSBhYnMoRikgPj0gMiBhbmQgLWxvZzEwIHAtdmFsdWUgPj0gMiAoYWxwaGEgPSAwLjAxKS4KU2lnbmlmaWNhbmNlIGhlcmUgbWVhbnMgZWZmZWN0IHNpemUgKEYpIG11bHRpcGxpZWQgYnkgLWxvZzEwIHAtdmFsdWUsIHRoZSB0aHJlc2hvbGQgaXMgaW5kaWNhdGVkIGJ5IHRoZSBkYXNoZWQgbGluZS4KCmBgYHtyLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNCwwfQpwbG90X25jUk5BX292ZXJ2aWV3IDwtIGRmX25jUk5BICU+JQogIGZpbHRlcih0aW1lID09IDApICU+JQogIG11dGF0ZShuY1JOQV90eXBlID0gZmFjdG9yKG5jUk5BX3R5cGUsIGMoImFzUk5BIiwgImlUU1MiLCAibmNSTkEiKSkpICU+JQogIGdncGxvdChhZXMoeCA9IHdtZWFuX2ZpdG5lc3MsIHkgPSAtbG9nMTAocF9maXRuZXNzX2FkaiksIGNvbG9yID0gbmNSTkFfdHlwZSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMC41KSArCiAgZ2VvbV9saW5lKGRhdGEgPSBkYXRhLmZyYW1lKHggPSBjKHNlcSgtOCwgLTAuNSwgMC4xKSwgc2VxKDAuNSwgOCwgMC4xKSksCiAgICB5ID0gNC9jKHNlcSg4LCAwLjUsIC0wLjEpLCBzZXEoMC41LCA4LCAwLjEpKSksCiAgICBhZXMoeCA9IHgsIHkgPSB5LCBzaGFwZSA9IE5VTEwsIGNvbCA9IE5VTEwpLCBsdHkgPSAyKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC03LCA3KSwgeWxpbSA9IGMoMCwgMykpICsKICBjdXN0b21fdGhlbWUoYXNwZWN0ID0gMSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBmYWNldF93cmFwKH4gY29uZGl0aW9uLCBuY29sID0gNikgKwogIGxhYnMoeCA9ICJmaXRuZXNzIiwgeSA9IGV4cHJlc3Npb24oIi1sb2ciWzEwXSoiIHAtdmFsdWUiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjdXN0b21fY29sb3JzKQoKcHJpbnQocGxvdF9uY1JOQV9vdmVydmlldykKYGBgCiMjIEFudGlzZW5zZSBSTkFzIGFuZCBpVFNTcwoKVGhlIGZpcnN0IHBhcnQgb2YgYSBtb3JlIGRldGFpbGVkIGFuYWx5c2lzIGlzIHRvIGV4dHJhY3QgYXNSTkFzIGFuZCBpVFNTcyB3aXRoIGRpZmZlcmVudGlhbCBmaXRuZXNzLCBhbmQgY29tcGFyZSB0aGVtIHRvIHRoZWlyIGFzc29jaWF0ZWQgZ2VuZXMuIFRoZSBhc3N1bXB0aW9uIGlzIHRoYXQgc2dSTkFzIHRhcmdldGluZyBhc1JOQXMvaVRTU3MgaW4gcmVhbGl0eSByZXByZXNzIHRyYW5zY3JpcHRpb24gb2YgdGhlaXIgcGFyZW50IGdlbmVzLCBhbmQgYnkgdGhlc2UgbWVhbnMgcHJvZHVjZSBhIGZpdG5lc3MgZWZmZWN0IHRoYXQgY2FuIG5vdCBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBhY3Rpb24gb2YgdGhlIGFzUk5BIGl0c2VsZi4gVGhlIGZpcnN0IHN0ZXAgaXMgZmlsdGVyIHRoZSBuY1JOQSBkYXRhc2V0IGFuZCBvcmRlciBuY1JOQXMgYnkgZml0bmVzcyBzaW1pbGFyaXR5LiBUaGUgY29tcGxldGUgZmlndXJlIGZvciB0aGUgYW5hbHlzaXMgZm9sbG93cyBhdCB0aGUgZW5kIG9mIHRoZSBjaGFwdGVyLgoKYGBge3IsIGZpZy53aWR0aCA9IDMuNSwgZmlnLmhlaWdodCA9IDh9CmRmX25jUk5BX3NlbGVjdCA8LSBkZl9uY1JOQSAlPiUKICBmaWx0ZXIodGltZSA9PSAwKSAlPiUKICBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JQogIGZpbHRlcihhbnkoY29tYl9zY29yZSA+PSA0KSkgJT4lCiAgdW5ncm91cApgYGAKClBsb3QgYXNSTkFzIGFuZCBpVFNTcyB2cyBnZW5lIGZpdG5lc3MuCgpgYGB7cn0KcGxvdF9uY1JOQXNfeHkgPC0gbGFwcGx5KGMoImFzUk5BIiwgImlUU1MiKSwgRlVOID0gZnVuY3Rpb24odHlwZSkgewogIGRmX25jUk5BX3NlbGVjdCAlPiUKICAgIGZpbHRlcihuY1JOQV90eXBlID09IHR5cGUpICU+JQogICAgbGVmdF9qb2luKGJ5ID0gYygiY29uZGl0aW9uIiwgImxvY3VzIiksCiAgICAgIHNlbGVjdChkZl9nZW5lLCBsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzLCBzZF9maXRuZXNzKSAlPiUgZGlzdGluY3QgJT4lCiAgICAgIHJlbmFtZShnZW5lX2ZpdG5lc3MgPSB3bWVhbl9maXRuZXNzLCBzZF9nZW5lX2ZpdG5lc3MgPSBzZF9maXRuZXNzKSkgJT4lCiAgICBzZWxlY3QobG9jdXMsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcywgZ2VuZV9maXRuZXNzLCBjb21iX3Njb3JlKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHdtZWFuX2ZpdG5lc3MsIHkgPSBnZW5lX2ZpdG5lc3MpKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGx0eSA9IDIpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDEuNSwgY29sb3IgPSBjdXN0b21fY29sb3JzWzVdKSArCiAgICBnZ3B1YnI6OnN0YXRfY29yKCkgKwogICAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC04LCA1KSwgeWxpbSA9IGMoLTgsIDUpKSArCiAgICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC44LCAwLjE1KSwgbGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjIsICJjbSIpKSArCiAgICBsYWJzKHggPSBwYXN0ZTAodHlwZSwgIiBmaXRuZXNzIiksIHkgPSAiZ2VuZSBmaXRuZXNzIikKfSkKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDYuNSwgZmlnLmhlaWdodCA9IDYuNSwgd2FybmluZyA9IEZBTFNFfQpnZ2FycmFuZ2UobnJvdyA9IDIsIGxhYmVscyA9IGMoIkEiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsIGhlaWdodHMgPSBjKDAuNTIsIDAuNDgpLAogIHBsb3RfbmNSTkFfb3ZlcnZpZXcgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIC0xMiwgMTIpLCAicG9pbnRzIikpLAogIGdnYXJyYW5nZShuY29sID0gMiwgbGFiZWxzID0gYygiQiIsICJDIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogICAgcGxvdF9uY1JOQXNfeHlbWzFdXSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgNiwgMTIpLCAicG9pbnRzIikpLAogICAgcGxvdF9uY1JOQXNfeHlbWzJdXSArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgNiwgMTIpLCAicG9pbnRzIikpCiAgKQopCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRSwgZXZhbCA9IFRSVUV9CnN2ZygiLi4vZmlndXJlcy9maWd1cmVTWF9hc1JOQV9pVFNTLnN2ZyIsIHdpZHRoID0gNi41LCBoZWlnaHQgPSA2LjUpCmdnYXJyYW5nZShucm93ID0gMiwgbGFiZWxzID0gYygiQSIpLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywgaGVpZ2h0cyA9IGMoMC41MiwgMC40OCksCiAgcGxvdF9uY1JOQV9vdmVydmlldyArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgLTEyLCAxMiksICJwb2ludHMiKSksCiAgZ2dhcnJhbmdlKG5jb2wgPSAyLCBsYWJlbHMgPSBjKCJCIiwgIkMiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgICBwbG90X25jUk5Bc194eVtbMV1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCA2LCAxMiksICJwb2ludHMiKSksCiAgICBwbG90X25jUk5Bc194eVtbMl1dICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCA2LCAxMiksICJwb2ludHMiKSkKICApCikKZGV2Lm9mZigpCmBgYAoKIyMgbm9uY29kaW5nIFJOQXMgYXMgcmVndWxhdG9yeSBlbGVtZW50cwoKVGhlIHNlY29uZCBwYXJ0IG9mIHRoaXMgYW5hbHlzaXMgaXMgdG8gbG9vayBhdCBub24tZ2VuZSBhc3NvY2lhdGVkIChpbnRlcmdlbmljKSBuY1JOQSBlbGVtZW50cy4gT2YgdGhlc2UsIHNldmVyYWwgYXJlIGtub3duIHRvIGhhdmUgYSByZWd1bGF0b3J5IGVmZmVjdC4gV2UgaW1wb3J0IGFuIGFsaWdubWVudCB0aGF0IHdhcyBkb25lIGluIGBweXRob25gIHVzaW5nIHRoZSBgYmlvcHl0aG9uYCBwYWNrYWdlLiBBbGwgbmNSTkFzIHdlcmUgYWxpZ25lZCB0byB0aGUgKlN5bmVjaG9jeXN0aXMqIGdlbm9tZSwgYW5kIHdlIGNhbiBub3cgZXh0cmFjdCB0aGUgZ2Vub21lIHBvc2l0aW9ucyB0byByZXRyaWV2ZSB0aGUgbmVpZ2hib3JpbmcgZ2VuZXMuCgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFLCBldmFsID0gVFJVRX0KZ2V0X25laWdoYm9ycyA8LSBmdW5jdGlvbihuLCB0aHJlc2hvbGQsIHR5cGUgPSAibG9naWNhbCIsCiAgbG93ZXJfbmVpZ2hib3IgPSBUUlVFLCB1cHBlcl9uZWlnaGJvciA9IFRSVUUpIHsKICBuIDwtIHNvcnQobikKICBsIDwtIHJlcF9hbG9uZyhuLCBGQUxTRSkKICBmb3IgKHRyIGluIHRocmVzaG9sZCkgewogICAgaWYgKHRyID4gdGFpbChuLCAxKSB8IHRyIDwgblsxXSkKICAgICAgc3RvcCgidGhyZXNob2xkIGV4Y2VlZHMgcmFuZ2Ugb2YgaW5wdXQgdmFsdWVzIikKICAgIGlmICh1cHBlcl9uZWlnaGJvcikgewogICAgICBsIDwtIGwgfCAhZHVwbGljYXRlZCh0ciA8IG4pfQogICAgaWYgKGxvd2VyX25laWdoYm9yKSB7CiAgICAgIGwgPC0gbCB8ICFkdXBsaWNhdGVkKHRyIDwgbiwgZnJvbUxhc3QgPSBUUlVFKX0KICAgIGlmICghKHRyID49IG5bMV0gJiB0ciA8IG5bMl0pKSBsWzFdIDwtIEZBTFNFCiAgICBpZiAoISh0ciA+IG5bbGVuZ3RoKG4pLTFdICYgdHIgPD0gbltsZW5ndGgobildKSl7CiAgICAgIGxbbGVuZ3RoKG4pXSA8LSBGQUxTRX0gCiAgfQogIGlmICh0eXBlID09ICJsb2dpY2FsIikgcmV0dXJuKGwpCiAgZWxzZSBpZiAodHlwZSA9PSAicG9zIikgcmV0dXJuKHdoaWNoKGwpKQogIGVsc2UgaWYgKHR5cGUgPT0gInZhbHVlIikgcmV0dXJuKG5bbF0pCn0KYGBgCgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBwYXJzZSB0aGUgKGltcG9ydGFudCkgaW5mb3JtYXRpb24gb2YgdGhlIGFsaWdubWVudCB0ZXh0IGZpbGUgaW50byBhIHRhYmxlLgoKYGBge3J9CmRmX2Fubm90YXRpb24gPC0gcmVhZF9jc3YoIi4uLy4uL3NnUk5BX2xpYnJhcnkvcmF3X2RhdGEvU3luZWNob2N5c3Rpc19QQ0M2ODAzX2dlbm9tZV9hbm5vdGF0aW9uXzIwMTkwNjE0LmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkgJT4lCiAgYXJyYW5nZShzdGFydF9icCkKCmRmX2FsaWdubWVudCA8LSByZWFkX2xpbmVzKCIuLi9kYXRhL291dHB1dC9uY1JOQV9hbGlnbm1lbnQudHh0IikgJT4lCiAgYXNfdGliYmxlICU+JQogIG11dGF0ZShuYW1lID0gcmVwKGMoImxvY3VzIiwgImdlbm9tZSIsICJnZW5vbWVfbGVuZ3RoIiwgInNjb3JlIiwgInNlcSIsICJhbGlnbm1lbnQiLCAiZ2Vub21lX3BvcyIsICJibGFuayIpLCBsZW5ndGgub3V0ID0gbigpKSkgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9ICJuYW1lIiwgdmFsdWVzX2Zyb20gPSAidmFsdWUiKSAlPiUKICB0aWR5cjo6dW5jaG9wKGNvbHMgPSBldmVyeXRoaW5nKCkpICU+JQogIG11dGF0ZShsb2N1cyA9IHN0cl9yZW1vdmUobG9jdXMsICJhbGlnbm1lbnQgb2Y6ICIpKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChnZW5vbWUsICI0NzExODMwNCIpKSAlPiUKICBzZWxlY3QoLWdlbm9tZV9sZW5ndGgsIC1zY29yZSwgLXNlcSwgLWFsaWdubWVudCwgLWJsYW5rKSAlPiUKICBtdXRhdGUoCiAgICBnZW5vbWVfc3RhcnQgPSBzdHJfZXh0cmFjdChnZW5vbWVfcG9zLCAiU2JqY3Q6ICtbMC05XSoiKSAlPiUKICAgIHN0cl9leHRyYWN0KCJbMC05XSsiKSAlPiUgYXMubnVtZXJpYykgJT4lCiAgbXV0YXRlKAogICAgZ2Vub21lX2VuZCA9IHN0cl9leHRyYWN0KGdlbm9tZV9wb3MsICJbQVRDR10gK1swLTldKiIpICU+JQogICAgc3RyX2V4dHJhY3QoIlswLTldKyIpICU+JSBhcy5udW1lcmljKSAlPiUKICBtdXRhdGUobGVuZ3RoID0gYWJzKGdlbm9tZV9lbmQtZ2Vub21lX3N0YXJ0KSkKCmhlYWQoZGZfYWxpZ25tZW50KQpgYGAKVGhlIHNlY29uZCBzdGVwIGlzIHRvIGxvb2sgdXAgdGhlIDUnIGFuZCAzJyBuZWlnaGJvcmluZyBnZW5lcyBmb3IgZWFjaCBuY1JOQS4gT25lIGNvdWxkIGFsc28gZG8gdGhpcyBtYW51YWxseSBidXQgaXQgYmVjb21lcyBpbXByYWN0aWNhbCBmb3Igc29tZXRoaW5nIGxpa2UgMTArIG5jUk5BIGxvY2kuIEFmdGVyIG9idGFpbmluZyBhIHRhYmxlIHdpdGggYm90aCB0aGUgbmNSTkEgaW4gb25lIGNvbHVtbiBhbmQgaXRzIGFzc29jaWF0ZWQgNScgYW5kIDMnIG5laWdoYm9yaW5nIGdlbmVzIGluIHNlcGFyYXRlIGNvbHVtbnMsIHdlIGNhbiBtYWtlIGEgc3VtbWFyeSBmaXRuZXNzIHRhYmxlIGFuZCBwbG90IGZpdG5lc3Mgb2YgbmNSTkEgYW5kIGl0cyBuZWlnaGJvcnMgYWdhaW5zdCBlYWNoIG90aGVyLgoKYGBge3J9CiMgZ2V0IG5laWdoYm9yaW5nIGdlbmVzCmRmX2FsaWdubWVudCA8LSBkZl9hbGlnbm1lbnQgJT4lCiAgYmluZF9jb2xzKAogICAgc2FwcGx5KGRmX2FsaWdubWVudCRnZW5vbWVfc3RhcnQsIGZ1bmN0aW9uKHgpIHsKICAgICAgZGYgPSBmaWx0ZXIoZGZfYW5ub3RhdGlvbiwgbG9jYXRpb24gPT0gIkNociIpCiAgICAgIG5laWdoYm9ycyA9IGdldF9uZWlnaGJvcnMobiA9IGRmJHN0YXJ0X2JwLCB0aHJlc2hvbGQgPSB4LCB0eXBlID0gInBvcyIpCiAgICAgIHVubGlzdChhcy5saXN0KGRmW25laWdoYm9ycywgIkdlbmVJRCJdKSkKICAgIH0pICU+JSB0ICU+JSBhc190aWJibGUKICApCgojIHNvbWUgam9pbiBvcGVyYXRpb25zIHRvIG1lcmdlIGRpZmZlcmVudCBmaXRuZXNzIGRhdGEgaW4gb25lIERGCmRmX25jUk5BX2NvbXAgPC0gZGZfbmNSTkFfc2VsZWN0ICU+JSBmaWx0ZXIobmNSTkFfdHlwZSA9PSAibmNSTkEiKSAlPiUKICBsZWZ0X2pvaW4ocmVuYW1lKGRmX2FsaWdubWVudCwgc2dSTkFfdGFyZ2V0ID0gbG9jdXMsIHVwc3RyZWFtID0gR2VuZUlEMSwgZG93bnN0cmVhbSA9IEdlbmVJRDIpICU+JQogICAgc2VsZWN0KHNnUk5BX3RhcmdldCwgdXBzdHJlYW0sIGRvd25zdHJlYW0pLAogICAgYnkgPSAic2dSTkFfdGFyZ2V0IikgJT4lCiAgbGVmdF9qb2luKGRmX2dlbmUgJT4lIHNlbGVjdChsb2N1cywgY29uZGl0aW9uLCB3bWVhbl9maXRuZXNzKSAlPiUKICAgIGRpc3RpbmN0ICU+JSByZW5hbWUodXBzdHJlYW0gPSBsb2N1cywgdXBzdHJlYW1fZ2VuZV9maXRuZXNzID0gd21lYW5fZml0bmVzcyksCiAgICBieSA9IGMoImNvbmRpdGlvbiIsICJ1cHN0cmVhbSIpCiAgKSAlPiUKICBsZWZ0X2pvaW4oZGZfZ2VuZSAlPiUgc2VsZWN0KGxvY3VzLCBjb25kaXRpb24sIHdtZWFuX2ZpdG5lc3MpICU+JQogICAgZGlzdGluY3QgJT4lIHJlbmFtZShkb3duc3RyZWFtID0gbG9jdXMsIGRvd25zdHJlYW1fZ2VuZV9maXRuZXNzID0gd21lYW5fZml0bmVzcyksCiAgICBieSA9IGMoImNvbmRpdGlvbiIsICJkb3duc3RyZWFtIikKICApCgojIGdldCBhIGxpc3Qgb2YgaW50ZXJlc3RpbmcgbmNSTkFzIHdoZXJlIHRoZSBuZWlnaGJvcmluZyBnZW5lCiMgaGFzIE5PIGVmZmVjdCBvbiBmaXRuZXNzCmxpc3RfbmNSTkFfaGl0cyA8LSBkZl9uY1JOQV9jb21wICU+JQogIGZpbHRlcigKICAgIGFicyh3bWVhbl9maXRuZXNzIC0gdXBzdHJlYW1fZ2VuZV9maXRuZXNzKSA+PSA0ICYKICAgIGFicyh3bWVhbl9maXRuZXNzIC0gZG93bnN0cmVhbV9nZW5lX2ZpdG5lc3MpID49IDQKICApICU+JSBncm91cF9ieShzZ1JOQV90YXJnZXQpICU+JSBjb3VudCAlPiUKICBmaWx0ZXIobiA+IDEpICU+JQogIHB1bGwoc2dSTkFfdGFyZ2V0KQpgYGAKClRoZSB0aGlyZCBzdGVwIGlzIHRvIHBsb3QgYSBoZWF0IG1hcCBhbmQgdGhlIGNvbXBhcmlzb24gb2YgdXBzdHJlYW0gYW5kIGRvd25zdHJlYW0gZ2VuZSBmaXRuZXNzLgoKYGBge3J9CnBsb3RfbmNSTkFfaGVhdCA8LSBkZl9uY1JOQV9zZWxlY3QgJT4lIGZpbHRlcihuY1JOQV90eXBlID09ICJuY1JOQSIpICU+JQogIG11dGF0ZShzZ1JOQV90YXJnZXQgPSBmY3RfY2x1c3RlcihzZ1JOQV90YXJnZXQsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcykpICU+JQogIG11dGF0ZSh3bWVhbl9maXRuZXNzID0gd21lYW5fZml0bmVzcyAlPiUgcmVwbGFjZSguLCAuID4gNCwgNCkgJT4lIHJlcGxhY2UoLiwgLiA8IC00LCAtNCkpICU+JQogIGdncGxvdChhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IHNnUk5BX3RhcmdldCwgZmlsbCA9IHdtZWFuX2ZpdG5lc3MpKSArCiAgZ2VvbV90aWxlKCkgKwogIGN1c3RvbV90aGVtZShsZWdlbmQucG9zID0gImJvdHRvbSIsCiAgICBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuNCwgImNtIiksIGxlZ2VuZC5tYXJnaW4gPSB1bml0KDAsICJjbSIpKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHZqdXN0ID0gMC41LCBoanVzdCA9IDEpKSArCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IGMoY3VzdG9tX2NvbG9yc1sxXSwgZ3JleSgwLjkpLCBjdXN0b21fY29sb3JzWzJdKSwKICAgIGxpbWl0cyA9IGMoLTQsIDQpKQoKIyBjaGVjayBjb3JyZWxhdGlvbiBvZiBuY1JOQSBmaXRuZXNzIHdpdGggdXBzdHJlYW0gYW5kIGRvd25zdHJlYW0gbHlpbmcgZ2VuZXMKcGxvdF9uY1JOQV91cHN0cmVhbSA8LSAKICBsYXBwbHkoYygidXBzdHJlYW1fZ2VuZV9maXRuZXNzIiwgImRvd25zdHJlYW1fZ2VuZV9maXRuZXNzIiksIEZVTiA9IGZ1bmN0aW9uKHZhcil7CiAgICBkZl9uY1JOQV9jb21wICU+JQogICAgbXV0YXRlKHNnUk5BX3RhcmdldCA9IGlmX2Vsc2UobG9jdXMgJWluJSBsaXN0X25jUk5BX2hpdHMsIHNnUk5BX3RhcmdldCwgIm90aGVyIikpICU+JQogICAgYXJyYW5nZShkZXNjKHNnUk5BX3RhcmdldCksIHdtZWFuX2ZpdG5lc3MpICU+JSAKICAgIGdncGxvdChhZXMoeCA9IHdtZWFuX2ZpdG5lc3MsIHkgPSBnZXQodmFyKSwgY29sb3IgPSBzZ1JOQV90YXJnZXQpKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGx0eSA9IDIpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLTQsIHNsb3BlID0gMSwgbHR5ID0gMikgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDEuNSkgKwogICAgZ2dwdWJyOjpzdGF0X2NvcihhZXMoeCA9IHdtZWFuX2ZpdG5lc3MsIHkgPSBnZXQodmFyKSwgY29sb3IgPSBOVUxMKSkgKwogICAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC04LCA1KSwgeWxpbSA9IGMoLTgsIDUpKSArCiAgICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC44LCAwLjIpLCBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuMiwgImNtIikpICsKICAgIGxhYnMoeCA9ICJuY1JOQSBmaXRuZXNzIiwgeSA9IHN0cl9yZXBsYWNlX2FsbCh2YXIsICJfIiwgIiAiKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGN1c3RvbV9jb2xvcnNbYygzLDQsNSldKQogIH0pCmBgYAoKCk1haW4gdGV4dCBmaWd1cmUgZm9yIG5jUk5BcyBvbmx5LgoKYGBge3IsIGZpZy53aWR0aCA9IDUuNSwgZmlnLmhlaWdodCA9IDYuNX0KZ2dhcnJhbmdlKG5jb2wgPSAyLCBsYWJlbHMgPSBjKCJBIiwgIkIiKSwgZm9udC5sYWJlbCA9IGxpc3RfZm9udHBhcnMsCiAgcGxvdF9uY1JOQV9oZWF0ICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAtMjAsIDEyKSwgInBvaW50cyIpKSwKICBnZ2FycmFuZ2UobnJvdyA9IDMsIGhlaWdodHMgPSBjKDAuNDMsIDAuNDMsIDAuMTQpLAogICAgbGFiZWxzID0gYygiIiwgIkMiLCAiIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogICAgcGxvdF9uY1JOQV91cHN0cmVhbVtbMV1dLAogICAgcGxvdF9uY1JOQV91cHN0cmVhbVtbMl1dCiAgKQopCmBgYAoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRSwgZXZhbCA9IFRSVUV9CnN2ZygiLi4vZmlndXJlcy9maWd1cmU0LnN2ZyIsIHdpZHRoID0gNS41LCBoZWlnaHQgPSA2LjUpCmdnYXJyYW5nZShuY29sID0gMiwgbGFiZWxzID0gYygiQSIsICJCIiksIGZvbnQubGFiZWwgPSBsaXN0X2ZvbnRwYXJzLAogIHBsb3RfbmNSTkFfaGVhdCArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgLTIwLCAxMiksICJwb2ludHMiKSksCiAgZ2dhcnJhbmdlKG5yb3cgPSAzLCBoZWlnaHRzID0gYygwLjQzLCAwLjQzLCAwLjE0KSwKICAgIGxhYmVscyA9IGMoIiIsICJDIiwgIiIpLCBmb250LmxhYmVsID0gbGlzdF9mb250cGFycywKICAgIHBsb3RfbmNSTkFfdXBzdHJlYW1bWzFdXSwKICAgIHBsb3RfbmNSTkFfdXBzdHJlYW1bWzJdXQogICkKKQpkZXYub2ZmKCkKYGBgCgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KZGZfbmNSTkFfc2VsZWN0ICU+JSBmaWx0ZXIobmNSTkFfdHlwZSA9PSAibmNSTkEiKSAlPiUKICBzZWxlY3QoIW1hdGNoZXMoInRpbWV8bG9nMkZvbGQiKSkgJT4lCiAgd3JpdGVfY3N2KCIuLi9kYXRhL291dHB1dC9maXRuZXNzX25jUk5BLmNzdiIpCmBgYAoKCiMgRXhwZXJpbWVudGFsIHZhbGlkYXRpb24gb2YgdGFyZ2V0cwoKIyMgR3Jvd3RoIGNoYXJhY3RlcmlzdGljcyBvZiBXVCwgQ1AxMiwgR0FQMSBhbmQgR0FQMiBtdXRhbnRzCgotIHdlIGZvdW5kIHRoYXQgQ1AxMiBwcm90ZWluIGBzc2wzMzY0YCBzaG93cyBpbnRlcmVzdGluZyBwaGVub3R5cGUgZm9yIGdsdWNvc2Utc3VwcGxlbWVudGVkIGdyb3d0aAotIENQMTIgS0QgbXV0YW50IHdhcyBtb3JlIHNlbnNpdGl2ZSB3aGVuIGdyb3duIG9uICtHIG9yICtHK0QKLSB3ZSBhbHNvIGZvdW5kIHRoYXQgZ2FwMSAoYHNscjA4ODRgKSBhbmQgZ2FwMiAoYHNsbDEzNDJgKSBLRCBtdXRhbnRzIHNob3cgZGlmZmVyZW50IHBoZW5vdHlwZSBjb21wYXJlZCB0byBwcmV2aW91cyByZXBvcnRzCi0gR2FwMSBtdXRhbnQgaGFkIG5vIHBoZW5vdHlwZSBhbHRob3VnaCBpdCB3YXMgcmVwb3J0ZWQgdG8gCgpgYGB7ciwgZmlnLndpZHRoID0gNS41LCBmaWcuaGVpZ2h0ID0gNC4wfQpkZl9nZW5lICU+JSBmaWx0ZXIobG9jdXMgJWluJSBjKCJzc2wzMzY0IiwgInNscjA4ODQiLCAic2xsMTM0MiIpKSAlPiUKICBtdXRhdGUoc2dSTkFfdGFyZ2V0ID0gcmVjb2RlKHNnUk5BX3RhcmdldCwgYHNzbDMzNjRgPSAiQ1AxMiIpKSAlPiUKICBwbG90X2dlbmVfZml0bmVzcyhuY29sID0gNiwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTcuNSwgMi45KSkKYGBgCi0gcmVhZCBpbiB2YWxpZGF0aW9uIGRhdGEKLSAzIHRhYmxlcyB3aXRoIE9EIG1lYXN1cmVtZW50IG9mIHRoZSBkZXNjcmliZWQgc3RyYWlucyBhbmQgY29uZGl0aW9uc8K0CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpkZl9vZCA8LSBsYXBwbHkoCiAgbGlzdC5maWxlcygiLi4vZGF0YS9pbnB1dC92YWxpZGF0aW9uLyIsIHBhdHRlcm4gPSAiXFxfT0QuY3N2IiwgZnVsbC5uYW1lcyA9IFRSVUUpLAogIGZ1bmN0aW9uKGYpIHsKICAgIHJlYWRfY3N2KGYsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JQogICAgcmVuYW1lX3dpdGgoLmZuID0gZnVuY3Rpb24oeCkgc3RyX3JlcGxhY2UoeCwgImJhdGNodGltZV9ofGhvdXJzIiwgInRpbWVfaCIpKSAlPiUKICAgIHBpdm90X2xvbmdlcigtdGltZV9oLCBuYW1lc190byA9ICJjb25kaXRpb24iLCB2YWx1ZXNfdG8gPSAiT0Q3MjAiKSAlPiUKICAgIG11dGF0ZShjb25kaXRpb24gPSBzdHJfcmVtb3ZlX2FsbChjb25kaXRpb24sICJbXFwuMC05XSokIikpICU+JQogICAgZ3JvdXBfYnkoY29uZGl0aW9uLCB0aW1lX2gpICU+JSBtdXRhdGUocmVwbGljYXRlID0gc2VxX2Fsb25nKE9ENzIwKSkgJT4lIAogICAgc2VwYXJhdGUoY29uZGl0aW9uLCBzZXAgPSAiXFxfIiwgaW50byA9IGMoIm11dGFudCIsICJjb25kaXRpb24iKSkKICB9CikgJT4lIHNldE5hbWVzKGMoIs6UQ1AxMiIsICLOlEdBUDEiLCAizpRHQVAyIikpICU+JQogIGJpbmRfcm93cyguaWQgPSAiY29tcGFyaXNvbiIpICU+JQogIG11dGF0ZShtdXRhbnQgPSByZWNvZGUobXV0YW50LCBgzpRzc2wzMzY0YCA9ICLOlENQMTIiKSkKCiMgb3B0aW9uYWxseSBzaW1wbGlmeSBuYW1lcyBvZiBjb25kaXRpb25zCmRmX29kIDwtIGRmX29kICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBjYXNlX3doZW4oCiAgICBzdHJfZGV0ZWN0KGNvbmRpdGlvbiwgIlxcK0csIFxcK0QkIikgfiAiaGV0ZXJvIiwKICAgIHN0cl9kZXRlY3QoY29uZGl0aW9uLCAiXFwrRyQiKSB+ICJtaXhvIiwKICAgIHN0cl9kZXRlY3QoY29uZGl0aW9uLCAiW0lITF17Mn0kIikgfiAicGhvdG8iCiAgKSkKYGBgCgotIHBsb3QgdGhlIGRhdGEgdXNpbmcgY3VzdG9tIGZ1bmN0aW9uCi0gZWFjaCBpbiBpdHMgb3duIGZpZWxkLCBiZWNhdXNlIGNvbmRpdGlvbnMgYW5kIG11dGFudHMgYXJlIGFsbCBkaWZmZXJlbnQKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9CmRmX29kX3N1bW1hcnkgPC0gZGZfb2QgJT4lCiAgZ3JvdXBfYnkodGltZV9oLCBjb21wYXJpc29uLCBjb25kaXRpb24sIG11dGFudCkgJT4lCiAgc3VtbWFyaXplKAogICAgbWVhbl9PRDcyMCA9IG1lYW4oT0Q3MjApLAogICAgc2RfT0Q3MjAgPSBzZChPRDcyMCksCiAgICB5bWluID0gbWVhbl9PRDcyMCAtIHNkX09ENzIwLAogICAgeW1heCA9IG1lYW5fT0Q3MjAgKyBzZF9PRDcyMAogICkKCnByaW50X29kIDwtIGZ1bmN0aW9uKGRhdGEsIGxvZyA9IEZBTFNFLCB5bGltID0gTlVMTCkgewogIGlmIChsb2cpIHsKICAgIGRhdGEgPC0gbXV0YXRlKGRhdGEsIGFjcm9zcyhtYXRjaGVzKCJtZWFufHltaW58eW1heCIpLCB+IGxvZygueCkpKQogIH0KICBkYXRhICU+JSB1bmdyb3VwICU+JQogICAgbXV0YXRlKAogICAgICBjb25kaXRpb24gPSBwYXN0ZTAoY29uZGl0aW9uLCAiICAiLCBtdXRhbnQpLAogICAgICBjb25kaXRpb24gPSBmYWN0b3IoY29uZGl0aW9uLCB1bmlxdWUoY29uZGl0aW9uKVtjKDMsNCwxLDIpXSkKICAgICkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSB0aW1lX2gsIHkgPSBtZWFuX09ENzIwLAogICAgICBjb2xvciA9IGNvbmRpdGlvbiwKICAgICAgZmlsbCA9IGNvbmRpdGlvbiwKICAgICAgeW1pbiA9IHltaW4sIHltYXggPSB5bWF4KSkgKwogICAgZ2VvbV9yaWJib24oYWxwaGEgPSAwLjIsIGxpbmV0eXBlID0gMCkgKwogICAgZ2VvbV9saW5lKHNpemUgPSAxKSArCiAgICBjdXN0b21fdGhlbWUobGVnZW5kLnBvcyA9IGMoMC4zNSwgMC44NSksIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC4xNSwgImNtIikpICsKICAgIGxhYnMoeCA9ICJ0aW1lIFtoXSIsIHkgPSBleHByZXNzaW9uKCJPRCJbNzIwXSkpICsKICAgIGNvb3JkX2NhcnRlc2lhbih5bGltID0geWxpbSkgKwogICAgZmFjZXRfd3JhcCggfiBjb21wYXJpc29uKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgZm9ybWF0KHgsIG5zbWFsbCA9IDIpKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzY2QTYxRSIsICIjYjRkODg5IiwgIiNFNzI5OEEiLCAiI2ViOTdjNCIpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCAiIzY2QTYxRSIsICIjYjRkODg5IiwgIiNFNzI5OEEiLCAiI2ViOTdjNCIpKQp9CmBgYAoKLSBjcmVhdGUgYSBzdW1tYXJ5IHRhYmxlIHdpdGggZ3Jvd3RocmF0ZXMKLSBjYWxjdWxhdGUgbWF4aW11bSBncm93dGggcmF0ZSBhbmQgYSBjb3JyZXNwb25kaW5nIHBsb3QKCmBgYHtyfQpkZl9tdSA8LSBkZl9vZCAlPiUKICBmaWx0ZXIodGltZV9oID49IDIwICYgdGltZV9oIDw9IDQwKSAlPiUKICBncm91cF9ieShjb21wYXJpc29uLCBjb25kaXRpb24sIG11dGFudCwgcmVwbGljYXRlKSAlPiUKICBzdW1tYXJpemUobXVfbWF4ID0gbG0obG9nKE9ENzIwKSB+IHRpbWVfaCkkY29lZmZpY2llbnRzWzJdKSAlPiUKICAjIGNhbGN1bGF0ZSB0LXRlc3QgcC12YWx1ZQogIGdyb3VwX2J5KGNvbXBhcmlzb24sIGNvbmRpdGlvbikgJT4lCiAgYXJyYW5nZShjb21wYXJpc29uLCBjb25kaXRpb24sIG11dGFudCkgJT4lIAogIG11dGF0ZShwX3ZhbHVlID0gdC50ZXN0KHggPSBtdV9tYXhbbXV0YW50ICE9ICJXVCJdLCB5ID0gbXVfbWF4W211dGFudCA9PSAiV1QiXSkkcC52YWx1ZSkgJT4lCiAgZ3JvdXBfYnkoY29tcGFyaXNvbiwgY29uZGl0aW9uLCBtdXRhbnQpICU+JQogICMgc3VtbWFyeSBzdGF0aXN0aWNzCiAgc3VtbWFyaXplKAogICAgbWVhbl9tdV9tYXggPSBtZWFuKG11X21heCksCiAgICBzZF9tdV9tYXggPSBzZChtdV9tYXgpLAogICAgeW1pbiA9IG1lYW5fbXVfbWF4IC0gc2RfbXVfbWF4LAogICAgeW1heCA9IG1lYW5fbXVfbWF4ICsgc2RfbXVfbWF4LAogICAgcF92YWx1ZSA9IHBfdmFsdWVbMV0KICApICU+JQogIG11dGF0ZShwX3ZhbHVlX3N5bWJvbCA9IGlmX2Vsc2UocF92YWx1ZSA8PSAwLjAxICYgbXV0YW50ICE9ICJXVCIsICIqIiwgIiIpKQpgYGAKCgpgYGB7cn0KcHJpbnRfbXUgPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGEgJT4lIHVuZ3JvdXAgJT4lCiAgICBtdXRhdGUoCiAgICAgIGNvbmRpdGlvbiA9IHBhc3RlMChjb25kaXRpb24sICIgICIsIG11dGFudCksCiAgICAgIGNvbmRpdGlvbiA9IGZhY3Rvcihjb25kaXRpb24sIHVuaXF1ZShjb25kaXRpb24pW2MoMyw0LDEsMildKQogICAgKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IGNvbmRpdGlvbiwgeSA9IG1lYW5fbXVfbWF4LAogICAgICBjb2xvciA9IGNvbmRpdGlvbiwKICAgICAgZmlsbCA9IGNvbmRpdGlvbiwKICAgICAgeW1pbiA9IHltaW4sIHltYXggPSB5bWF4KSkgKwogICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCB3aWR0aCA9IDAuNikgKwogICAgZ2VvbV9lcnJvcmJhcihwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42LCBzaXplID0gMSkgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBfdmFsdWVfc3ltYm9sKSwgc2l6ZSA9IDgsIG51ZGdlX3kgPSAwLjAwMykgKwogICAgY3VzdG9tX3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IDApICsKICAgIGxhYnMoeCA9ICIiLCB5ID0gZXhwcmVzc2lvbigiwrUgW2giXi0xKiJdIikpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzUsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKwogICAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsMC4wNSkpICsKICAgIGZhY2V0X3dyYXAoIH4gY29tcGFyaXNvbikgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiM2NkE2MUUiLCAiI2I0ZDg4OSIsICIjRTcyOThBIiwgIiNlYjk3YzQiKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzY2QTYxRSIsICIjYjRkODg5IiwgIiNFNzI5OEEiLCAiI2ViOTdjNCIpKQp9CmBgYAoKLSBzcGVjaWZpYyBwbG90IGZvciBzbWFsbCBmaXRuZXNzIHNjb3JlIGZpZ3VyZXMKLSBvbmx5IHRoZSAyIHNlbGVjdGVkIGNvbmRpdGlvbnMgZnJvbSBncm93dGggZGF0YQoKYGBge3J9CnByaW50X2ZpdG5lc3MgPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGEgJT4lIAogIHNlbGVjdChnZW5lX25hbWUsIGNvbmRpdGlvbiwgd21lYW5fZml0bmVzcywgc2RfZml0bmVzcykgJT4lCiAgICBkaXN0aW5jdCAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjb25kaXRpb24sIHkgPSB3bWVhbl9maXRuZXNzLAogICAgICBjb2xvciA9IGNvbmRpdGlvbiwgZmlsbCA9IGNvbmRpdGlvbiwKICAgICAgeW1pbiA9IHdtZWFuX2ZpdG5lc3MgLSBzZF9maXRuZXNzLAogICAgICB5bWF4ID0gd21lYW5fZml0bmVzcyArIHNkX2ZpdG5lc3MpKSArCiAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gMC42KSArCiAgICBnZW9tX2Vycm9yYmFyKHBvc2l0aW9uID0gImRvZGdlIiwgd2lkdGggPSAwLjYsIHNpemUgPSAxKSArCiAgICBjdXN0b21fdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gMCkgKwogICAgbGFicyh4ID0gIiIsIHkgPSAiZml0bmVzcyIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzUsIHZqdXN0ID0gMSwgaGp1c3QgPSAxKSkgKwogICAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKC02LCA0KSkgKwogICAgZmFjZXRfd3JhcCggfiBnZW5lX25hbWUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjRkODg5IiwgIiNlYjk3YzQiKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2I0ZDg4OSIsICIjZWI5N2M0IikpCn0KYGBgCgotIHBsb3QgZXZlcnl0aGluZyBvbiBhIGxhcmdlIGNhbnZhcwotIHRoaXMgd2lsbCBiZWNvbWUgdGhlIHZhbGlkYXRpb24gZmlndXJlIAoKYGBge3IsIGZpZy5oZWlnaHQgPSA5LCBmaWcud2lkdGggPSA5LCB3YXJuaW5nID0gRkFMU0V9CmdnYXJyYW5nZShuY29sID0gMywgbnJvdyA9IDMsCiAgcHJpbnRfb2QoZmlsdGVyKGRmX29kX3N1bW1hcnksIGNvbXBhcmlzb24gPT0gIs6UR0FQMSIpLCB5bGltID0gYygwLCAxLjYpKSwKICBwcmludF9vZChmaWx0ZXIoZGZfb2Rfc3VtbWFyeSwgY29tcGFyaXNvbiA9PSAizpRHQVAyIiksIHlsaW0gPSBjKDAsIDEuMCkpLAogIHByaW50X29kKGZpbHRlcihkZl9vZF9zdW1tYXJ5LCBjb21wYXJpc29uID09ICLOlENQMTIiKSwgeWxpbSA9IGMoMCwgMS4wKSksCiAgcHJpbnRfb2QoZmlsdGVyKGRmX29kX3N1bW1hcnksIGNvbXBhcmlzb24gPT0gIs6UR0FQMSIpLCB5bGltID0gYygtMi41LCAwLjUpLCBsb2cgPSBUUlVFKQogICAgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIDEyLCA4KSwicG9pbnRzIikpLAogIHByaW50X29kKGZpbHRlcihkZl9vZF9zdW1tYXJ5LCBjb21wYXJpc29uID09ICLOlEdBUDIiKSwgeWxpbSA9IGMoLTIuNSwgMC41KSwgbG9nID0gVFJVRSkKICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMiwgOCksInBvaW50cyIpKSwKICBwcmludF9vZChmaWx0ZXIoZGZfb2Rfc3VtbWFyeSwgY29tcGFyaXNvbiA9PSAizpRDUDEyIiksIHlsaW0gPSBjKC0yLjUsIDAuNSksIGxvZyA9IFRSVUUpCiAgICArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMTIsIDgpLCJwb2ludHMiKSksCiAgZ2dhcnJhbmdlKG5jb2wgPSAyLCB3aWR0aHMgPSBjKDAuNjUsIDAuMzUpLAogICAgcHJpbnRfbXUoZmlsdGVyKGRmX211LCBjb21wYXJpc29uID09ICLOlEdBUDEiKSksCiAgICBwcmludF9maXRuZXNzKGZpbHRlcihkZl9nZW5lLCBsb2N1cyA9PSAic2xyMDg4NCIsIGNvbmRpdGlvbiAlaW4lIGMoIkhDLCBMTCIsICJIQywgTEwsICtHIikpKQogICAgICArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMTksIC00KSwicG9pbnRzIikpCiAgKSwKICBnZ2FycmFuZ2UobmNvbCA9IDIsIHdpZHRocyA9IGMoMC42NSwgMC4zNSksCiAgICBwcmludF9tdShmaWx0ZXIoZGZfbXUsIGNvbXBhcmlzb24gPT0gIs6UR0FQMiIpKQogICAgICArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMTAsIDEyKSwicG9pbnRzIikpLAogICAgcHJpbnRfZml0bmVzcyhmaWx0ZXIoZGZfZ2VuZSwgbG9jdXMgPT0gInNsbDEzNDIiLCBjb25kaXRpb24gJWluJSBjKCJMQywgSUwiLCAiTEMsIExMLCArRCwgK0ciKSkpCiAgICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMCwgLTQpLCJwb2ludHMiKSkKICApLAogIGdnYXJyYW5nZShuY29sID0gMiwgd2lkdGhzID0gYygwLjY1LCAwLjM1KSwKICAgIHByaW50X211KGZpbHRlcihkZl9tdSwgY29tcGFyaXNvbiA9PSAizpRDUDEyIikpCiAgICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMSwgOCksInBvaW50cyIpKSwKICAgIHByaW50X2ZpdG5lc3MoZmlsdGVyKGRmX2dlbmUsIGxvY3VzID09ICJzc2wzMzY0IiwgY29uZGl0aW9uICVpbiUgYygiTEMsIExMIiwgIkxDLCBMTCwgK0QsICtHIikpKQogICAgICArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMTAsIC00KSwicG9pbnRzIikpCiAgKQopCmBgYAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQpzdmcoIi4uL2ZpZ3VyZXMvZmlndXJlM192YWxpZGF0aW9uLnN2ZyIsIHdpZHRoID0gOSwgaGVpZ2h0ID0gOSkKZ2dhcnJhbmdlKG5jb2wgPSAzLCBucm93ID0gMywKICBwcmludF9vZChmaWx0ZXIoZGZfb2Rfc3VtbWFyeSwgY29tcGFyaXNvbiA9PSAizpRHQVAxIiksIHlsaW0gPSBjKDAsIDEuNikpLAogIHByaW50X29kKGZpbHRlcihkZl9vZF9zdW1tYXJ5LCBjb21wYXJpc29uID09ICLOlEdBUDIiKSwgeWxpbSA9IGMoMCwgMS4wKSksCiAgcHJpbnRfb2QoZmlsdGVyKGRmX29kX3N1bW1hcnksIGNvbXBhcmlzb24gPT0gIs6UQ1AxMiIpLCB5bGltID0gYygwLCAxLjApKSwKICBwcmludF9vZChmaWx0ZXIoZGZfb2Rfc3VtbWFyeSwgY29tcGFyaXNvbiA9PSAizpRHQVAxIiksIHlsaW0gPSBjKC0yLjUsIDAuNSksIGxvZyA9IFRSVUUpCiAgICArIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDEyLCAxMiwgMTIsIDgpLCJwb2ludHMiKSksCiAgcHJpbnRfb2QoZmlsdGVyKGRmX29kX3N1bW1hcnksIGNvbXBhcmlzb24gPT0gIs6UR0FQMiIpLCB5bGltID0gYygtMi41LCAwLjUpLCBsb2cgPSBUUlVFKQogICAgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIDEyLCA4KSwicG9pbnRzIikpLAogIHByaW50X29kKGZpbHRlcihkZl9vZF9zdW1tYXJ5LCBjb21wYXJpc29uID09ICLOlENQMTIiKSwgeWxpbSA9IGMoLTIuNSwgMC41KSwgbG9nID0gVFJVRSkKICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMiwgOCksInBvaW50cyIpKSwKICBnZ2FycmFuZ2UobmNvbCA9IDIsIHdpZHRocyA9IGMoMC42NSwgMC4zNSksCiAgICBwcmludF9tdShmaWx0ZXIoZGZfbXUsIGNvbXBhcmlzb24gPT0gIs6UR0FQMSIpKSwKICAgIHByaW50X2ZpdG5lc3MoZmlsdGVyKGRmX2dlbmUsIGxvY3VzID09ICJzbHIwODg0IiwgY29uZGl0aW9uICVpbiUgYygiSEMsIExMIiwgIkhDLCBMTCwgK0ciKSkpCiAgICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxOSwgLTQpLCJwb2ludHMiKSkKICApLAogIGdnYXJyYW5nZShuY29sID0gMiwgd2lkdGhzID0gYygwLjY1LCAwLjM1KSwKICAgIHByaW50X211KGZpbHRlcihkZl9tdSwgY29tcGFyaXNvbiA9PSAizpRHQVAyIikpCiAgICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMCwgMTIpLCJwb2ludHMiKSksCiAgICBwcmludF9maXRuZXNzKGZpbHRlcihkZl9nZW5lLCBsb2N1cyA9PSAic2xsMTM0MiIsIGNvbmRpdGlvbiAlaW4lIGMoIkxDLCBJTCIsICJMQywgTEwsICtELCArRyIpKSkKICAgICAgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIDEwLCAtNCksInBvaW50cyIpKQogICksCiAgZ2dhcnJhbmdlKG5jb2wgPSAyLCB3aWR0aHMgPSBjKDAuNjUsIDAuMzUpLAogICAgcHJpbnRfbXUoZmlsdGVyKGRmX211LCBjb21wYXJpc29uID09ICLOlENQMTIiKSkKICAgICAgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxMiwgMTIsIDExLCA4KSwicG9pbnRzIikpLAogICAgcHJpbnRfZml0bmVzcyhmaWx0ZXIoZGZfZ2VuZSwgbG9jdXMgPT0gInNzbDMzNjQiLCBjb25kaXRpb24gJWluJSBjKCJMQywgTEwiLCAiTEMsIExMLCArRCwgK0ciKSkpCiAgICAgICsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoMTIsIDEyLCAxMCwgLTQpLCJwb2ludHMiKSkKICApCikKZGV2Lm9mZigpCmBgYAoKCiMgRXhwb3J0IHN1bW1hcnkgdGFibGUgb2YgYWxsIGdlbmVzIGFuZCBjb25kaXRpb25zCgpFeHBvcnQgYSBzdW1tYXJ5IHRhYmxlIG9mIGFsbCBnZW5lcyBhbmQgY29uZGl0aW9ucywgc28gdGhhdCBpdCdzIGVhc3kgZm9yIG90aGVyIHBlb3BsZSB0byBsb29rIHVwIHNpbmdsZSBjb25kaXRpb25zIGFzIGZvciBleGFtcGxlIGRvbmUgaW4gW29uZS1ieS1vbmUgZml0bmVzcyBjb21wYXJpc29uc10oI2ZpdG5lc3Mtb2YtYWxsLWNvbmRpdGlvbnMtdnMtZWFjaC1vdGhlcikuIFRoaXMgaXMgYmVzdCBkb25lIGluIHdpZGUgZm9ybWF0IChvbmUgY29sdW1uIHBlciBjb25kaXRpb24pLgoKYGBge3J9CmRmX2dlbmUgJT4lIHVuZ3JvdXAgJT4lCiAgZmlsdGVyKHNnUk5BX3R5cGUgPT0gImdlbmUiKSAlPiUKICBzZWxlY3QobG9jdXMsIHNnUk5BX3RhcmdldCwgZ2VuZV9uYW1lLCBjb25kaXRpb24sIHdtZWFuX2ZpdG5lc3MpICU+JSAKICBkaXN0aW5jdCAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY29uZGl0aW9uLCB2YWx1ZXNfZnJvbSA9IHdtZWFuX2ZpdG5lc3MpICU+JQogIHdyaXRlX2NzdigiLi4vZGF0YS9vdXRwdXQvZml0bmVzc19zdW1tYXJ5LmNzdiIpCgpkZl9nZW5lICU+JQogIGZpbHRlcihzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgd3JpdGVfY3N2KCIuLi9kYXRhL291dHB1dC9maXRuZXNzX2dlbmVzLmNzdiIpCgpkZl9rZWdnICU+JSB3cml0ZV9jc3YoIi4uL2RhdGEvb3V0cHV0L2tlZ2dfYW5ub3RhdGlvbi5jc3YiKQoKZGZfdW5pcHJvdCAlPiUgd3JpdGVfY3N2KCIuLi9kYXRhL291dHB1dC91bmlwcm90X2Fubm90YXRpb24uY3N2IikKCmRmX2xpbnJlZ19maWx0ZXJlZCAlPiUgd3JpdGVfY3N2KCIuLi9kYXRhL291dHB1dC9saW5lYXJfcmVncmVzc2lvbl9yZXN1bHQuY3N2IikKYGBgCgpUaGUgZW50aXJlIHBpcGVsaW5lIHRha2VzIGFib3V0IDUgbWludXRlcyB0byBydW4gb24gYSBzdGFuZGFyZCBub3RlYm9vay4KVG8gd29yayBvbiBzaW5nbGUgc2VjdGlvbnMsIHRoZSB3b3JrIHNwYWNlIGlzIGV4cG9ydGVkIHRvIGF2b2lkIGNvbnN0YW50IHJlY2FsY3VsYXRpb24gb2YgcmVzdWx0IHRhYmxlcy4KCmBgYHtyLCBpbmNsdWRlID0gRkFMU0UsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQojIHRoaXMgaGlkZGVuIGNvZGUgY2h1bmsgc2ltcGx5IGV4cG9ydHMgdGhlIGZpdG5lc3MgZGF0YSBpbiBhIGRpZmZlcmVudCBmb3JtYXQKIyBzdWl0YWJsZSBmb3IgZGVwb3NpdGlvbiBvbiB0aGUgU2hpbnlMaWIgYXBwIAojIEFwcDogaHR0cHM6Ly9tLWphaG4uc2hpbnlhcHBzLmlvL1NoaW55TGliLwojIEdpdGh1YjogaHR0cHM6Ly9naXRodWIuY29tL20tamFobi9TaGlueUxpYgpDUklTUFJpX2xpYnJhcnlfMjAyMiA8LSBkZl9tYWluICU+JSB1bmdyb3VwICU+JQogIGZpbHRlcihzZ1JOQV90eXBlID09ICJnZW5lIikgJT4lCiAgc2VsZWN0KC1ncm91cCwgLXJlZmVyZW5jZV9ncm91cCwgLWJhc2VNZWFuLCAtcHJvdGVpbiwgLWxlbmd0aCwgLW1hc3MsIC1lY19udW1iZXIpICU+JQogIGxlZnRfam9pbihieSA9ICJsb2N1cyIsIGRmX2Fubm90YXRpb24gJT4lCiAgICBzZWxlY3QoR2VuZUlELCBQcm9jZXNzLCBQYXRod2F5LCBQcm90ZWluKSAlPiUKICAgIHJlbmFtZShsb2N1cyA9IEdlbmVJRCwgcHJvY2VzcyA9IFByb2Nlc3MsIHBhdGh3YXkgPSBQYXRod2F5LCBwcm90ZWluID0gUHJvdGVpbikpICU+JQogIG11dGF0ZShGb2xkQ2hhbmdlID0gMl5sb2cyRm9sZENoYW5nZSkKCiMgc2F2ZSBhcyBSZGF0YSBmaWxlCnNhdmUoQ1JJU1BSaV9saWJyYXJ5XzIwMjIsIGZpbGUgPSAiLi4vLi4vLi4vU2hpbnlMaWIvZGF0YS9DUklTUFJpX2xpYnJhcnlfMjAyMi5SZGF0YSIpCgojIHNhdmUgYXMgY3N2IGZpbGUgZm9yIE1MIGFwcGxpY2F0aW9uCkNSSVNQUmlfbGlicmFyeV8yMDIyICU+JSBzZWxlY3QoCiAgc2dSTkEsIHNnUk5BX3RhcmdldCwgc2dSTkFfcG9zaXRpb24sIGNvbmRpdGlvbiwKICB0aW1lLCBsb2cyRm9sZENoYW5nZSwgZml0bmVzcywgc2dSTkFfaW5kZXgsIHNnUk5BX3R5cGUsIGxvY3VzLAogIHNnUk5BX2NvcnJlbGF0aW9uLCBzZ1JOQV9lZmZpY2llbmN5KSAlPiUKICB3cml0ZV9jc3YoZmlsZSA9ICIuLi9kYXRhL291dHB1dC9maXRuZXNzX3NnUk5BLmNzdiIpCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CiMgcmVtb3ZlIGxhcmdlIGludGVybWVkaWF0ZSBvYmplY3RzCmlmICgibGlzdF9jb25kaXRpb25fcGFpcnMiICVpbiUgbHMoKSkgcm0oImxpc3RfY29uZGl0aW9uX3BhaXJzIikKCiMgZXhwb3J0IHdvcmtzcGFjZQpzYXZlKGxpc3QgPSBscygpLCBmaWxlID0gIi4uL3BpcGVsaW5lL0NSSVNQUmlfVjJfZGF0YV9wcm9jZXNzaW5nLlJkYXRhIikKYGBgCgoKIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK